[PATCH 12/13] net/txgbe: fix CR/KR link training and recovery

Zaiyu Wang zaiyuwang at trustnetic.com
Thu Aug 27 13:42:04 CEST 2026


Backplane CR/KR link training on the E56 PHY is unreliable and does
not recover after a link event: the completion poll can block for a
second on a condition that never triggers, the page exchange is only
run once and its result is assumed to stay valid, and the FFE init
mode and calibration ordering are also wrong.

- Poll the AN FSM (0x78010, value 0x9) for CL72 completion with a
  1 ms step and a 400 ms budget, replacing the ephy 0x163c mask-0xe
  poll that tested the wrong condition; the per-lane TX-FFE dumps
  move into txgbe_e56_get_txffe(), called once AN completes.
- Re-run the page exchange on every AN next-page interrupt from the
  event handler (50 x 1 ms poll, explicit next-page handshake,
  -ETIMEDOUT on expiry) and only enter training after it succeeds;
  the inline exchange in the AN73 flow is removed and a CL72 failure
  no longer aborts the flow, so every link event re-runs the
  exchange instead of assuming the previous one is still valid.
- Initialize the FFE init mode bits in cfg_40g()/cfg_10g(), run
  txgbe_e56_set_rxs_ufine_le_max() after the RXS osc init, and write
  0x78001 = 0x7 in the AN programming sequence.
- Clear hw->bp_link_mode in txgbe_set_link_to_sfi() so a port that
  trained on the backplane does not keep a stale backplane mode
  after switching to SFI.

Fixes: 234ce0d1fa9d ("net/txgbe: fix link stability for Amber-Lite backplane mode")
Cc: stable at dpdk.org

Signed-off-by: Zaiyu Wang <zaiyuwang at trustnetic.com>
---
 drivers/net/txgbe/base/txgbe_e56.c    |   3 +
 drivers/net/txgbe/base/txgbe_e56.h    |   1 +
 drivers/net/txgbe/base/txgbe_e56_bp.c | 161 +++++++++++++++-----------
 drivers/net/txgbe/base/txgbe_e56_bp.h |   2 +
 drivers/net/txgbe/base/txgbe_phy.c    |   3 +
 drivers/net/txgbe/txgbe_ethdev.c      |  28 ++++-
 6 files changed, 129 insertions(+), 69 deletions(-)

diff --git a/drivers/net/txgbe/base/txgbe_e56.c b/drivers/net/txgbe/base/txgbe_e56.c
index 2711863f2c..8e986daf96 100644
--- a/drivers/net/txgbe/base/txgbe_e56.c
+++ b/drivers/net/txgbe/base/txgbe_e56.c
@@ -551,6 +551,7 @@ u32 txgbe_e56_cfg_40g(struct txgbe_hw *hw)
 
 	addr  = E56PHY_FETX_FFE_TRAIN_CFG_0_ADDR;
 	rdata = rd32_ephy(hw, addr);
+	set_fields_e56(&rdata, E56PHY_FETX_FFE_TRAIN_CFG_0_KRT_FETX_INIT_FFE_CFG_0, 0x2);
 	set_fields_e56(&rdata, E56PHY_FETX_FFE_TRAIN_CFG_0_KRT_FETX_INIT_FFE_CFG_2, 0x2);
 	wr32_ephy(hw, addr, rdata);
 
@@ -1377,6 +1378,8 @@ txgbe_e56_cfg_10g(struct txgbe_hw *hw)
 
 	addr = E56PHY_FETX_FFE_TRAIN_CFG_0_ADDR;
 	rdata = rd32_ephy(hw, addr);
+	set_fields_e56(&rdata, E56PHY_FETX_FFE_TRAIN_CFG_0_KRT_FETX_INIT_FFE_CFG_0,
+		       0x2);
 	set_fields_e56(&rdata, E56PHY_FETX_FFE_TRAIN_CFG_0_KRT_FETX_INIT_FFE_CFG_2,
 		       0x2);
 	wr32_ephy(hw, addr, rdata);
diff --git a/drivers/net/txgbe/base/txgbe_e56.h b/drivers/net/txgbe/base/txgbe_e56.h
index fe9a83ae01..32d95b61b9 100644
--- a/drivers/net/txgbe/base/txgbe_e56.h
+++ b/drivers/net/txgbe/base/txgbe_e56.h
@@ -435,6 +435,7 @@ typedef union {
 #define E56PHY_KRT_TFSM_CFGKRT_TFSM_HOLDOFF_TIMER_X256K 23, 16
 
 #define E56PHY_FETX_FFE_TRAIN_CFG_0_ADDR   (E56PHY_PMD_BASE_ADDR + 0x2BC)
+#define E56PHY_FETX_FFE_TRAIN_CFG_0_KRT_FETX_INIT_FFE_CFG_0 1, 0
 #define E56PHY_FETX_FFE_TRAIN_CFG_0_KRT_FETX_INIT_FFE_CFG_2 9, 8
 #define E56PHY_FETX_FFE_TRAIN_CFG_0_KRT_FETX_INIT_FFE_CFG_3 13, 12
 
diff --git a/drivers/net/txgbe/base/txgbe_e56_bp.c b/drivers/net/txgbe/base/txgbe_e56_bp.c
index d376d918df..f840e6b5dd 100644
--- a/drivers/net/txgbe/base/txgbe_e56_bp.c
+++ b/drivers/net/txgbe/base/txgbe_e56_bp.c
@@ -813,6 +813,7 @@ static int txgbe_e56_phy_rxs_calib_adapt_seq(struct txgbe_hw *hw,
 		status |= txgbe_e56_ctle_bypass_seq(hw, bp_link_mode);
 
 	status |= txgbe_e56_rxs_osc_init_for_temp_track_range(hw, bp_link_mode);
+	txgbe_e56_set_rxs_ufine_le_max(hw, bp_link_mode);
 
 	/* Wait an fsm_rx_sts 25G */
 	BP_LOG("Wait CTRL_FSM_RX_STAT[0]::ctrl_fsm_rx0_st to be ready ...\n");
@@ -2123,6 +2124,7 @@ int txgbe_e56_set_phy_link_mode(struct txgbe_hw *hw,
 	set_fields_e56(&rdata, 12, 12, 0x1);
 	wr32_epcs(hw, 0x070000, rdata);
 	wr32_epcs(hw, 0x078002, 0x0000);
+	wr32_epcs(hw, 0x78001, 0x7);
 	/* pcs case fec en to work around first */
 	wr32_epcs(hw, 0x100ab, 1);
 
@@ -2351,24 +2353,29 @@ static int chk_bkp_ability(struct txgbe_hw *hw,
 	return 0;
 }
 
-static int txgbe_e56_exchange_page(struct txgbe_hw *hw)
+int txgbe_e56_exchange_page(struct txgbe_hw *hw)
 {
 	struct txgbe_backplane_ability local_ability = {0}, lp_ability = {0};
 	u32 an_int, base_page = 0;
-	int count = 0;
+	int count = 0, count2 = 0;
 
 	an_int = rd32_epcs(hw, 0x78002);
-	/* 500ms timeout */
 	if (!(an_int & VR_AN_INTR_PG_RCV))
 		return -EINVAL;
 
-	for (count = 0; count < 500; count++) {
+	/* 50ms timeout */
+	for (count = 0; count < 50; count++) {
 		u32 fsm = rd32_epcs(hw, 0x78010);
-		u32 rdata = rd32_epcs(hw, 0x78002);
+		u32 next_page = 0;
+		u32 rdata;
+
+		count2++;
 
 		BP_LOG("-----count----- %d - fsm: %x\n", count, fsm);
-		BP_LOG("read 78002 data %0x and clear pacv\n", rdata);
+		rdata = rd32_epcs(hw, 0x78002);
+		/* clear an pacv int */
 		an_int = rdata;
+		BP_LOG("read 78002 data %0x and clear pacv\n", rdata);
 		set_fields_e56(&rdata, 2, 2, 0x0);
 		wr32_epcs(hw, 0x78002, rdata);
 		if (an_int & VR_AN_INTR_PG_RCV) {
@@ -2383,31 +2390,84 @@ static int txgbe_e56_exchange_page(struct txgbe_hw *hw)
 					wr32_epcs(hw, 0x70016, 0x2001);
 					BP_LOG("write 70016 0x%0x\n",
 					       0x2001);
+					next_page = 1;
+					count = 0; /* reset count to wait next page */
+				} else {
+					next_page = 0;
 				}
 				base_page = 1;
 			}
 		}
-		if ((fsm & 0x8) == 0x8) {
-			hw->fsm = 0x8;
-			goto check_ability;
+		if (!next_page) {
+			if ((fsm & 0x8) == 0x8) {
+				hw->fsm = 0x8;
+				goto check_ability;
+			}
 		}
-		usec_delay(100);
+		usec_delay(1000);
 	}
 
 check_ability:
+	if (count == 50) {
+		BP_LOG("Wait for next page timeout\n");
+		return -ETIMEDOUT;
+	}
+	BP_LOG("AN exchange page done in %d ms\n", count2);
 	return chk_bkp_ability(hw, local_ability, lp_ability);
 }
 
+void txgbe_e56_get_txffe(struct txgbe_hw *hw)
+{
+	/* 21. read txffe to check kr training status */
+	u32 rdata = 0, pmd_ctrl = 0, lane_idx = 0, lane_num = 0, txffe = 0;
+
+	switch (hw->bp_link_mode) {
+	case 10:
+		lane_num = 1;
+		break;
+	case 40:
+		lane_num = 4;
+		break;
+	case 25:
+		lane_num = 1;
+		break;
+	default:
+		BP_LOG("%s %d :Invalid speed\n", __func__, __LINE__);
+		return;
+	}
+
+	BP_LOG("%dG phy kr training check.... fsm: %x\n",
+	       hw->bp_link_mode, rd32_epcs(hw, 0x78010));
+	rdata = rd32_ephy(hw, 0x163c) & GENMASK(lane_num, 1);
+	pmd_ctrl = rd32_ephy(hw, 0x1644);
+	BP_LOG("KR TRAINNING CHECK = %x. pmd_ctrl:%lx-%lx-%lx-%lx\n",
+	       rdata,
+	       FIELD_GET_M(GENMASK(3, 0), pmd_ctrl),
+	       FIELD_GET_M(GENMASK(7, 4), pmd_ctrl),
+	       FIELD_GET_M(GENMASK(11, 8), pmd_ctrl),
+	       FIELD_GET_M(GENMASK(15, 12), pmd_ctrl));
+	BP_LOG("before: %x-%x-%x-%x\n",
+	       rd32_ephy(hw, 0x141c), rd32_ephy(hw, 0x1420),
+	       rd32_ephy(hw, 0x1424), rd32_ephy(hw, 0x1428));
+	for (lane_idx = 0; lane_idx < lane_num; lane_idx++) {
+		txffe = rd32_ephy(hw, 0x828 + lane_idx * 0x100);
+		BP_LOG("after[%x]: %lx-%lx-%lx-%lx\n", lane_idx,
+		       FIELD_GET_M(GENMASK(6, 0), txffe),
+		       FIELD_GET_M(GENMASK(21, 16), txffe),
+		       FIELD_GET_M(GENMASK(29, 24), txffe),
+		       FIELD_GET_M(GENMASK(13, 8), txffe));
+	}
+}
+
 static int txgbe_e56_cl72_training(struct txgbe_hw *hw)
 {
 	u32 bylinkmode = hw->bp_link_mode;
 	u8 bypass_ctle = hw->bypass_ctle;
 	int status = 0, temp_data = 0;
-	u32 lane_num = 0, lane_idx = 0;
-	u32 __rte_unused pmd_ctrl = 0, txffe = 0;
+	u32 lane_num = 0;
+	u32 __rte_unused pmd_ctrl = 0;
 	int ret = 0;
 	u32 rdata;
-
 	u8 pll_en_cfg = 0;
 	u8 pmd_mode = 0;
 
@@ -2463,52 +2523,48 @@ static int txgbe_e56_cl72_training(struct txgbe_hw *hw)
 
 	/* 18 */
 	/* 19. rxs calibration and adaptation sequence */
-	BP_LOG("2.4 Wait %dG RXS.... fsm: %x\n",
-	       bylinkmode, rd32_epcs(hw, 0x78010));
+	BP_LOG("2.4 Wait %dG RXS.... fsm: %x, an_int: %x\n",
+	       bylinkmode, rd32_epcs(hw, 0x78010),
+	       rd32_epcs(hw, 0x78002));
 	status = txgbe_e56_phy_rxs_calib_adapt_seq(hw, bylinkmode, bypass_ctle);
 	ret |= status;
 	/* 20 */
-	BP_LOG("2.5 Wait %dG phy calibration.... fsm: %x\n",
-	       bylinkmode, rd32_epcs(hw, 0x78010));
-	txgbe_e56_set_rxs_ufine_le_max(hw, bylinkmode);
+	BP_LOG("2.5 Wait %dG phy calibration.... fsm: %x, an_int: %x\n",
+	       bylinkmode, rd32_epcs(hw, 0x78010),
+	       rd32_epcs(hw, 0x78002));
 	status = txgbe_e56_get_temp(hw, &temp_data);
 	if (bylinkmode == 40)
 		status = txgbe_temp_track_seq_40g(hw, TXGBE_LINK_SPEED_40GB_FULL);
 	else
 		status = txgbe_e56_rxs_post_cdr_lock_temp_track_seq(hw, bylinkmode);
+
+	ret |= status;
 	/* 21 */
-	BP_LOG("2.6 Wait %dG phy kr training check.... fsm: %x\n",
-	       bylinkmode, rd32_epcs(hw, 0x78010));
-	status = kr_read_poll(rd32_ephy, rdata,
-				  ((rdata & 0xe) & GENMASK(lane_num, 1)) ==
-				  (0xe & GENMASK(lane_num, 1)), 100,
-				   10000, hw, 0x163c);
+	BP_LOG("2.6 Wait %dG phy kr fsm check : %x, an_int: %x\n",
+	       bylinkmode, rd32_epcs(hw, 0x78010),
+	       rd32_epcs(hw, 0x78002));
+	status = kr_read_poll(rd32_epcs, rdata,
+			      (rdata & 0x9) == 0x9, 1000,
+			      400, hw, 0x78010);
 	pmd_ctrl = rd32_ephy(hw, 0x1644);
-	BP_LOG("KR TRAINING CHECK = %x, %s. pmd_ctrl:%lx-%lx-%lx-%lx\n",
+	BP_LOG("KR FSM CHECK = %x, %s. pmd_ctrl:%lx-%lx-%lx-%lx\n",
 	       rdata, status ? "FAILED" : "SUCCESS",
 	       FIELD_GET_M(GENMASK(3, 0), pmd_ctrl),
 	       FIELD_GET_M(GENMASK(7, 4), pmd_ctrl),
 	       FIELD_GET_M(GENMASK(11, 8), pmd_ctrl),
 	       FIELD_GET_M(GENMASK(15, 12), pmd_ctrl));
 	ret |= status;
-	BP_LOG("before: %x-%x-%x-%x\n",
-	       rd32_ephy(hw, 0x141c), rd32_ephy(hw, 0x1420),
-	       rd32_ephy(hw, 0x1424), rd32_ephy(hw, 0x1428));
-
-	for (lane_idx = 0; lane_idx < lane_num; lane_idx++) {
-		txffe = rd32_ephy(hw, 0x828 + lane_idx * 0x100);
-		BP_LOG("after[%x]: %lx-%lx-%lx-%lx\n", lane_idx,
-		       FIELD_GET_M(GENMASK(6, 0), txffe),
-		       FIELD_GET_M(GENMASK(21, 16), txffe),
-		       FIELD_GET_M(GENMASK(29, 24), txffe),
-		       FIELD_GET_M(GENMASK(13, 8), txffe));
-	}
 
 	/* 22 */
-	BP_LOG("2.7 Wait %dG phy Rx adc.... fsm:%x\n",
-	       bylinkmode, rd32_epcs(hw, 0x78010));
+	BP_LOG("2.7 Wait %dG phy Rx adc.... fsm:%x, an_int: %x\n",
+	       bylinkmode, rd32_epcs(hw, 0x78010),
+	       rd32_epcs(hw, 0x78002));
 	status = txgbe_e56_rxs_adc_adapt_seq(hw, bypass_ctle);
 
+	BP_LOG("2.8 ===end ret : %d.... fsm:%x, an_int: %x\n",
+	       ret, rd32_epcs(hw, 0x78010),
+	       rd32_epcs(hw, 0x78002));
+
 	return ret;
 }
 
@@ -2517,27 +2573,7 @@ int txgbe_handle_e56_bkp_an73_flow(struct txgbe_hw *hw)
 	int status = 0;
 	u32 rdata;
 
-	BP_LOG("2.1 Wait page changed ....\n");
-	status = txgbe_e56_exchange_page(hw);
-	if (status) {
-		BP_LOG("Exchange page failed\n");
-		return status;
-	}
-
-	BP_LOG("2.2 Wait page changed ..done..\n");
-	wr32_epcs(hw, 0x100ab, 0);
-	if (AN_TRAINING_MODE) {
-		rdata = rd32_epcs(hw, 0x70000);
-		BP_LOG("read 0x70000 data %0x\n", rdata);
-		wr32_epcs(hw, 0x70000, 0);
-		BP_LOG("write 0x70000 0x%0x\n", 0);
-	}
-
-	rdata = rd32_epcs(hw, 0x78002);
-	BP_LOG("read 78002 data %0x and clear page int\n", rdata);
-	set_fields_e56(&rdata, 2, 2, 0x0);
-	wr32_epcs(hw, 0x78002, rdata);
-
+	/* 10  RXS_DISABLE - TXS_DISABLE - CMS_DISABLE */
 	/* dis phy tx/rx lane */
 	rdata = rd32_ephy(hw, 0x1400);
 	set_fields_e56(&rdata, 19, 16, 0x0);
@@ -2583,11 +2619,6 @@ int txgbe_handle_e56_bkp_an73_flow(struct txgbe_hw *hw)
 	}
 
 	status = txgbe_e56_cl72_training(hw);
-	if (status) {
-		BP_LOG("CL72 training failed, status = %d\n", status);
-		return status;
-	}
-
 	rdata = rd32_ephy(hw, E56PHY_RXS_IDLE_DETECT_1_ADDR);
 	set_fields_e56(&rdata, E56PHY_RXS_IDLE_DETECT_1_IDLE_TH_ADC_PEAK_MAX, 0x28);
 	set_fields_e56(&rdata, E56PHY_RXS_IDLE_DETECT_1_IDLE_TH_ADC_PEAK_MIN, 0xa);
diff --git a/drivers/net/txgbe/base/txgbe_e56_bp.h b/drivers/net/txgbe/base/txgbe_e56_bp.h
index d2c49c2fce..da0d02b79f 100644
--- a/drivers/net/txgbe/base/txgbe_e56_bp.h
+++ b/drivers/net/txgbe/base/txgbe_e56_bp.h
@@ -279,4 +279,6 @@ typedef union {
 int txgbe_e56_set_phy_link_mode(struct txgbe_hw *hw,
 				u8 bp_link_mode, u32 need_restart);
 int txgbe_handle_e56_bkp_an73_flow(struct txgbe_hw *hw);
+int txgbe_e56_exchange_page(struct txgbe_hw *hw);
+void txgbe_e56_get_txffe(struct txgbe_hw *hw);
 #endif
diff --git a/drivers/net/txgbe/base/txgbe_phy.c b/drivers/net/txgbe/base/txgbe_phy.c
index 8b653f0aeb..10df23afae 100644
--- a/drivers/net/txgbe/base/txgbe_phy.c
+++ b/drivers/net/txgbe/base/txgbe_phy.c
@@ -1947,6 +1947,9 @@ txgbe_set_link_to_sfi(struct txgbe_hw *hw,
 	s32 err = 0;
 	u32 value = 0;
 
+	/* Switching to SFI mode clears backplane link mode. */
+	hw->bp_link_mode = 0;
+
 	/* Set the module link speed */
 	hw->mac.set_rate_select_speed(hw, speed);
 	/* 1. Wait xpcs power-up good */
diff --git a/drivers/net/txgbe/txgbe_ethdev.c b/drivers/net/txgbe/txgbe_ethdev.c
index ef30e0c40a..269e0aadd9 100644
--- a/drivers/net/txgbe/txgbe_ethdev.c
+++ b/drivers/net/txgbe/txgbe_ethdev.c
@@ -3025,6 +3025,7 @@ void txgbe_dev_e56_check_bp_event(void *param)
 	u32 __rte_unused an_int = 0;
 	int ret = 0;
 	bool need_link_update = false;
+	bool exchange_done = false;
 
 	if (!hw)
 		return;
@@ -3043,8 +3044,10 @@ void txgbe_dev_e56_check_bp_event(void *param)
 		hw->phy.sfp_type = txgbe_sfp_type_not_present;
 	}
 
-	if (!(txgbe_xpcs_an_enabled(hw)))
+	if (!(txgbe_xpcs_an_enabled(hw))) {
+		BP_LOG("%s %d\n", __func__, __LINE__);
 		return;
+	}
 
 	if (!hw->devarg.auto_neg)
 		return;
@@ -3063,6 +3066,7 @@ void txgbe_dev_e56_check_bp_event(void *param)
 		need_link_update = true;
 		value &= ~VR_AN_INTR_CMPLT;
 		wr32_epcs(hw, VR_AN_INTR, value);
+		txgbe_e56_get_txffe(hw);
 	}
 
 	if (value & VR_AN_INTR_LINK) {
@@ -3080,7 +3084,21 @@ void txgbe_dev_e56_check_bp_event(void *param)
 	}
 
 	if (value & VR_AN_INTR_PG_RCV) {
-		BP_LOG("%d Enter training\n", hw->port_id);
+		BP_LOG("%d 2.1 *** Wait page changed ....\n", hw->port_id);
+		ret = txgbe_e56_exchange_page(hw);
+		if (ret) {
+			BP_LOG("%d 2.2 *** Exchange page failed\n", hw->port_id);
+			goto an_status;
+		} else {
+			BP_LOG("%d 2.2 *** Wait page changed ..done..\n",
+			       hw->port_id);
+			wr32_epcs(hw, 0x100ab, 0);
+			exchange_done = true;
+		}
+	}
+
+	if (exchange_done) {
+		BP_LOG("%d 2.2.2 *** Enter training\n", hw->port_id);
 		ret = txgbe_handle_e56_bkp_an73_flow(hw);
 		if (!AN_TRAINING_MODE) {
 			fsm = rd32_epcs(hw, 0x78010);
@@ -3783,8 +3801,10 @@ txgbe_dev_interrupt_get_status(struct rte_eth_dev *dev,
 	if (eicr & TXGBE_ICRMISC_LSC)
 		intr->flags |= TXGBE_FLAG_NEED_LINK_UPDATE;
 
-	if (eicr & TXGBE_ICRMISC_ANDONE)
-		intr->flags |= TXGBE_FLAG_NEED_AN_CONFIG;
+	if (eicr & TXGBE_ICRMISC_ANDONE) {
+		PMD_DRV_LOG(DEBUG, "an int eicr=0x%08x", eicr);
+		intr->flags |= TXGBE_FLAG_NEED_AN_CONFIG;//aml40-to-do
+	}
 
 	if (eicr & TXGBE_ICRMISC_VFMBX)
 		intr->flags |= TXGBE_FLAG_MAILBOX;
-- 
2.55.0.windows.2



More information about the dev mailing list