During 10GBASE-KR link training, the PHY state machine can be corrupted if device stop or rate change operations are initiated while training is in progress. This manifests as: - Link stability issues after interface down/up cycles - PHY state machine lockups requiring a full driver reset - Intermittent link failures on Inphi re-driver configurations The root cause is that the firmware mailbox operations for device stop and rate changes can interfere with ongoing KR training sequences, leaving the PHY in an inconsistent state. Add synchronization to prevent device operations from interrupting active KR training: - Introduce a mailbox mutex to serialize firmware command access - Wait for KR training completion (or timeout) before proceeding with stop/rate change operations - Only wait when KR training is actually active (KR mode with autoneg enabled or Inphi re-driver present) - Use a 500ms timeout to handle hung training sequences The mailbox mutex protects the critical section of firmware command submission and completion checking, preventing concurrent mailbox access from multiple code paths. Testing on AMD platforms with both direct-attach and Inphi re-driver configurations shows this eliminates PHY state corruption during interface operations and link changes. Fixes: 549b32af9f7c ("amd-xgbe: Simplify mailbox interface rate change code") Signed-off-by: Raju Rangoju --- Changes since v2: - address the changes requested by AI review: https://sashiko.dev/#/message/20260318091608.1266381-1-Raju.Rangoju%40amd.com Changes since v1: - use scoped_guard() instead of guard() for functions longer than 20 lines drivers/net/ethernet/amd/xgbe/xgbe-drv.c | 7 +- drivers/net/ethernet/amd/xgbe/xgbe-main.c | 1 + drivers/net/ethernet/amd/xgbe/xgbe-phy-v2.c | 85 ++++++++++++++++++++- drivers/net/ethernet/amd/xgbe/xgbe.h | 13 ++++ 4 files changed, 100 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-drv.c b/drivers/net/ethernet/amd/xgbe/xgbe-drv.c index 23beea48ae26..67dcb72ac776 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-drv.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-drv.c @@ -1312,15 +1312,18 @@ static int xgbe_start(struct xgbe_prv_data *pdata) static void xgbe_stop(struct xgbe_prv_data *pdata) { - struct xgbe_hw_if *hw_if = &pdata->hw_if; struct xgbe_phy_if *phy_if = &pdata->phy_if; - struct xgbe_channel *channel; struct net_device *netdev = pdata->netdev; + struct xgbe_hw_if *hw_if = &pdata->hw_if; + struct xgbe_channel *channel; struct netdev_queue *txq; unsigned int i; DBGPR("-->xgbe_stop\n"); + /* Wait for any active KR training to complete */ + xgbe_wait_for_kr_training(pdata); + if (test_bit(XGBE_STOPPED, &pdata->dev_state)) return; diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-main.c b/drivers/net/ethernet/amd/xgbe/xgbe-main.c index 7d45ea22a02e..5f3ab29707b7 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-main.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-main.c @@ -78,6 +78,7 @@ struct xgbe_prv_data *xgbe_alloc_pdata(struct device *dev) spin_lock_init(&pdata->xpcs_lock); mutex_init(&pdata->rss_mutex); + mutex_init(&pdata->mailbox_lock); spin_lock_init(&pdata->tstamp_lock); mutex_init(&pdata->i2c_mutex); init_completion(&pdata->i2c_complete); diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-phy-v2.c b/drivers/net/ethernet/amd/xgbe/xgbe-phy-v2.c index b8cf6ccfe641..ed524703dd8c 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-phy-v2.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-phy-v2.c @@ -2095,8 +2095,75 @@ static void xgbe_phy_pll_ctrl(struct xgbe_prv_data *pdata, bool enable) usleep_range(100, 200); } +static bool xgbe_phy_port_is_inphi(struct xgbe_prv_data *pdata) +{ + struct xgbe_phy_data *phy_data = pdata->phy_data; + + /* Re-driver models 4223 && 4227 are supported Inphi models */ + return phy_data->redrv && + (phy_data->redrv_model == XGBE_PHY_REDRV_MODEL_4223 || + phy_data->redrv_model == XGBE_PHY_REDRV_MODEL_4227); +} + +static bool xgbe_kr_training_in_progress(struct xgbe_prv_data *pdata) +{ + struct xgbe_phy_data *phy_data = pdata->phy_data; + unsigned long kr_start, kr_end; + + /* Only wait for KR training in specific conditions: + * - Inphi re-driver is present, OR + * - Currently in KR mode with autoneg enabled + */ + if (!xgbe_phy_port_is_inphi(pdata) && + !(phy_data->cur_mode == XGBE_MODE_KR && + pdata->phy.autoneg == AUTONEG_ENABLE)) + return false; + + /* If training hasn't completed, ensure it actually started */ + kr_start = READ_ONCE(pdata->kr_start_time); + if (!kr_start) + return false; + + /* Training is complete - no need to wait */ + if (READ_ONCE(pdata->an_result) == XGBE_AN_COMPLETE) + return false; + + kr_end = kr_start + + msecs_to_jiffies(XGBE_AN_MS_TIMEOUT + XGBE_KRTR_TIME); + + /* If we're already past the training window, it's not "in progress" */ + if (time_after(jiffies, kr_end)) + return false; + + return true; +} + +static void xgbe_wait_for_kr_training_inprogress(struct xgbe_prv_data *pdata) +{ + unsigned long kr_end; + + if (!xgbe_kr_training_in_progress(pdata)) + return; + + /* Don't block the auto-negotiation state machine work item */ + if (current_work() == &pdata->an_work) + return; + + kr_end = READ_ONCE(pdata->kr_start_time) + + msecs_to_jiffies(XGBE_AN_MS_TIMEOUT + XGBE_KRTR_TIME); + + /* Poll until training completes or the training window expires */ + while (time_before(jiffies, kr_end)) { + if (READ_ONCE(pdata->an_result) == XGBE_AN_COMPLETE) + break; + + usleep_range(10000, 11000); + } +} + static void xgbe_phy_perform_ratechange(struct xgbe_prv_data *pdata, - enum xgbe_mb_cmd cmd, enum xgbe_mb_subcmd sub_cmd) + enum xgbe_mb_cmd cmd, + enum xgbe_mb_subcmd sub_cmd) { unsigned int s0 = 0; unsigned int wait; @@ -2104,6 +2171,13 @@ static void xgbe_phy_perform_ratechange(struct xgbe_prv_data *pdata, /* Disable PLL re-initialization during FW command processing */ xgbe_phy_pll_ctrl(pdata, false); + /* Serialize firmware mailbox access. + * Protects entire command sequence including busy check, PLL control, + * and command execution. Uses explicit lock/unlock for compatibility + * with goto-based cleanup (per cleanup.h guidelines). + */ + mutex_lock(&pdata->mailbox_lock); + /* Log if a previous command did not complete */ if (XP_IOREAD_BITS(pdata, XP_DRIVER_INT_RO, STATUS)) { netif_dbg(pdata, link, pdata->netdev, @@ -2115,7 +2189,7 @@ static void xgbe_phy_perform_ratechange(struct xgbe_prv_data *pdata, XP_SET_BITS(s0, XP_DRIVER_SCRATCH_0, COMMAND, cmd); XP_SET_BITS(s0, XP_DRIVER_SCRATCH_0, SUB_COMMAND, sub_cmd); - /* Issue the command */ + /* Issue the firmware command */ XP_IOWRITE(pdata, XP_DRIVER_SCRATCH_0, s0); XP_IOWRITE(pdata, XP_DRIVER_SCRATCH_1, 0); XP_IOWRITE_BITS(pdata, XP_DRIVER_INT_REQ, REQUEST, 1); @@ -2123,11 +2197,13 @@ static void xgbe_phy_perform_ratechange(struct xgbe_prv_data *pdata, /* Wait for command to complete */ wait = XGBE_RATECHANGE_COUNT; while (wait--) { - if (!XP_IOREAD_BITS(pdata, XP_DRIVER_INT_RO, STATUS)) + if (!XP_IOREAD_BITS(pdata, XP_DRIVER_INT_RO, STATUS)) { + mutex_unlock(&pdata->mailbox_lock); goto do_rx_adaptation; - + } usleep_range(1000, 2000); } + mutex_unlock(&pdata->mailbox_lock); netif_dbg(pdata, link, pdata->netdev, "firmware mailbox command did not complete\n"); @@ -3743,6 +3819,7 @@ void xgbe_init_function_ptrs_phy_v2(struct xgbe_phy_if *phy_if) phy_impl->kr_training_pre = xgbe_phy_kr_training_pre; phy_impl->kr_training_post = xgbe_phy_kr_training_post; + phy_impl->kr_training_inprogress = xgbe_wait_for_kr_training_inprogress; phy_impl->module_info = xgbe_phy_module_info; phy_impl->module_eeprom = xgbe_phy_module_eeprom; diff --git a/drivers/net/ethernet/amd/xgbe/xgbe.h b/drivers/net/ethernet/amd/xgbe/xgbe.h index 438033a71523..be92f0607d28 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe.h +++ b/drivers/net/ethernet/amd/xgbe/xgbe.h @@ -203,6 +203,9 @@ #define XGBE_LINK_TIMEOUT 5 #define XGBE_KR_TRAINING_WAIT_ITER 50 +/* Extra slack time beyond AN timeout to cover KR training completion */ +#define XGBE_KRTR_TIME 100 + #define XGBE_SGMII_AN_LINK_DUPLEX BIT(1) #define XGBE_SGMII_AN_LINK_SPEED (BIT(2) | BIT(3)) #define XGBE_SGMII_AN_LINK_SPEED_10 0x00 @@ -844,6 +847,7 @@ struct xgbe_phy_impl_if { /* Pre/Post KR training enablement support */ void (*kr_training_pre)(struct xgbe_prv_data *); void (*kr_training_post)(struct xgbe_prv_data *); + void (*kr_training_inprogress)(struct xgbe_prv_data *pdata); /* SFP module related info */ int (*module_info)(struct xgbe_prv_data *pdata, @@ -1015,6 +1019,9 @@ struct xgbe_prv_data { /* RSS addressing mutex */ struct mutex rss_mutex; + /* Firmware mailbox mutex */ + struct mutex mailbox_lock; + /* Flags representing xgbe_state */ unsigned long dev_state; @@ -1252,6 +1259,12 @@ struct xgbe_prv_data { }; /* Function prototypes*/ +static inline void xgbe_wait_for_kr_training(struct xgbe_prv_data *pdata) +{ + if (pdata->phy_if.phy_impl.kr_training_inprogress) + pdata->phy_if.phy_impl.kr_training_inprogress(pdata); +} + struct xgbe_prv_data *xgbe_alloc_pdata(struct device *); void xgbe_free_pdata(struct xgbe_prv_data *); void xgbe_set_counts(struct xgbe_prv_data *); -- 2.34.1