From d77e98f8b2b382b06be7f17e482480dd8c4c5046 Mon Sep 17 00:00:00 2001 From: Shuaisong Yang Date: Wed, 24 Jun 2026 22:13:16 +0800 Subject: [PATCH 1/4] net: hns3: unify copper port ksettings configuration path Refactor hns3_set_link_ksettings() and hclge_set_phy_link_ksettings() to unify the configuration path for copper ports. Previously, netdevs with a native kernel phy attached bypassed the main MAC parameter caching logic and returned early via phy_ethtool_ksettings_set(). This prevented the driver from updating hdev->hw.mac.req_xxx variables for kernel PHY setups, leaving them out-of-sync during reset recovery. Clean this up by routing all copper port configurations through ops->set_phy_link_ksettings(), and perform driver-level or kernel-level PHY arbitration inside hclge_set_phy_link_ksettings() via hnae3_dev_phy_imp_supported(). This ensures that the user's intended link profiles (req_speed, req_duplex, req_autoneg) are uniformly recorded across all copper and fiber deployment topologies, laying the groundwork for stable reset recovery. For copper ports where neither IMP firmware nor a kernel PHY is available (e.g. PHY_INEXISTENT), hclge_set_phy_link_ksettings() returns -ENODEV. In hns3_set_link_ksettings(), this is caught so the configuration falls through to the existing MAC-level path (check_ksettings_param -> cfg_mac_speed_dup_h), preserving compatibility with PHY-less copper deployments. Signed-off-by: Shuaisong Yang Signed-off-by: Jijie Shao Link: https://patch.msgid.link/20260624141319.271439-2-shaojijie@huawei.com Signed-off-by: Jakub Kicinski --- .../ethernet/hisilicon/hns3/hns3_ethtool.c | 31 +++++++++---------- .../hisilicon/hns3/hns3pf/hclge_main.c | 28 +++++++++++++++-- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c b/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c index 9cb7ce9fd311..442f15476af3 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c @@ -811,12 +811,11 @@ static int hns3_get_link_ksettings(struct net_device *netdev, } static int hns3_check_ksettings_param(const struct net_device *netdev, - const struct ethtool_link_ksettings *cmd) + const struct ethtool_link_ksettings *cmd, + u8 media_type) { struct hnae3_handle *handle = hns3_get_handle(netdev); const struct hnae3_ae_ops *ops = hns3_get_ops(handle); - u8 module_type = HNAE3_MODULE_TYPE_UNKNOWN; - u8 media_type = HNAE3_MEDIA_TYPE_UNKNOWN; u32 lane_num; u8 autoneg; u32 speed; @@ -836,9 +835,6 @@ static int hns3_check_ksettings_param(const struct net_device *netdev, return 0; } - if (ops->get_media_type) - ops->get_media_type(handle, &media_type, &module_type); - if (cmd->base.duplex == DUPLEX_HALF && media_type != HNAE3_MEDIA_TYPE_COPPER) { netdev_err(netdev, @@ -863,6 +859,8 @@ static int hns3_set_link_ksettings(struct net_device *netdev, struct hnae3_handle *handle = hns3_get_handle(netdev); struct hnae3_ae_dev *ae_dev = hns3_get_ae_dev(handle); const struct hnae3_ae_ops *ops = hns3_get_ops(handle); + u8 module_type = HNAE3_MODULE_TYPE_UNKNOWN; + u8 media_type = HNAE3_MEDIA_TYPE_UNKNOWN; int ret; /* Chip don't support this mode. */ @@ -878,22 +876,23 @@ static int hns3_set_link_ksettings(struct net_device *netdev, cmd->base.autoneg, cmd->base.speed, cmd->base.duplex, cmd->lanes); - /* Only support ksettings_set for netdev with phy attached for now */ - if (netdev->phydev) { - if (cmd->base.speed == SPEED_1000 && - cmd->base.autoneg == AUTONEG_DISABLE) - return -EINVAL; + if (!ops->get_media_type) + return -EOPNOTSUPP; + ops->get_media_type(handle, &media_type, &module_type); - return phy_ethtool_ksettings_set(netdev->phydev, cmd); - } else if (test_bit(HNAE3_DEV_SUPPORT_PHY_IMP_B, ae_dev->caps) && - ops->set_phy_link_ksettings) { - return ops->set_phy_link_ksettings(handle, cmd); + if (media_type == HNAE3_MEDIA_TYPE_COPPER) { + if (!ops->set_phy_link_ksettings) + return -EOPNOTSUPP; + ret = ops->set_phy_link_ksettings(handle, cmd); + if (ret != -ENODEV) + return ret; + /* PHY_INEXISTENT, use MAC-level configuration */ } if (ae_dev->dev_version < HNAE3_DEVICE_VERSION_V2) return -EOPNOTSUPP; - ret = hns3_check_ksettings_param(netdev, cmd); + ret = hns3_check_ksettings_param(netdev, cmd, media_type); if (ret) return ret; diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c index 2f1984930da2..9fe6bc02d71e 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c @@ -3285,8 +3285,8 @@ static int hclge_get_phy_link_ksettings(struct hnae3_handle *handle, } static int -hclge_set_phy_link_ksettings(struct hnae3_handle *handle, - const struct ethtool_link_ksettings *cmd) +hclge_ethtool_ksettings_set(struct hnae3_handle *handle, + const struct ethtool_link_ksettings *cmd) { struct hclge_desc desc[HCLGE_PHY_LINK_SETTING_BD_NUM]; struct hclge_vport *vport = hclge_get_vport(handle); @@ -3327,10 +3327,32 @@ hclge_set_phy_link_ksettings(struct hnae3_handle *handle, return ret; } + linkmode_copy(hdev->hw.mac.advertising, cmd->link_modes.advertising); + return 0; +} + +static int +hclge_set_phy_link_ksettings(struct hnae3_handle *handle, + const struct ethtool_link_ksettings *cmd) +{ + struct hclge_vport *vport = hclge_get_vport(handle); + struct hclge_dev *hdev = vport->back; + int ret = -ENODEV; + + if (hnae3_dev_phy_imp_supported(hdev)) { + ret = hclge_ethtool_ksettings_set(handle, cmd); + } else if (handle->netdev->phydev) { + if (cmd->base.speed == SPEED_1000 && + cmd->base.autoneg == AUTONEG_DISABLE) + return -EINVAL; + ret = phy_ethtool_ksettings_set(handle->netdev->phydev, cmd); + } + if (ret) + return ret; + hdev->hw.mac.req_autoneg = cmd->base.autoneg; hdev->hw.mac.req_speed = cmd->base.speed; hdev->hw.mac.req_duplex = cmd->base.duplex; - linkmode_copy(hdev->hw.mac.advertising, cmd->link_modes.advertising); return 0; } From c01f6e6bdc1ccd21b2d07d23f50b82437b8cbf88 Mon Sep 17 00:00:00 2001 From: Shuaisong Yang Date: Wed, 24 Jun 2026 22:13:17 +0800 Subject: [PATCH 2/4] net: hns3: refactor MAC autoneg and speed configuration Extract the MAC autoneg and speed/duplex/lane configuration logic out of hclge_mac_init() and encapsulate it into a new dedicated helper function hclge_set_autoneg_speed_dup(). In the init path (hclge_init_ae_dev), this helper is now called after hclge_update_port_info() so that firmware-reported autoneg values are already populated before applying the link configuration. Introduce a separate req_lane_num field in struct hclge_mac to isolate the user-requested lane count from mac.lane_num, which firmware may overwrite via hclge_get_sfp_info() with stale values from a prior link lifecycle (e.g., lane_num=4 from 100G). During probe, req_lane_num is initialized to 0, which instructs firmware to auto-select the correct lane count for the current speed, rather than reusing the firmware- reported mac.lane_num that may be inconsistent with the target speed. This prevents probe failures from mismatched (speed, lane_num) pairs. In the reset path (hclge_reset_ae_dev), it runs immediately after hclge_mac_init(), using the previously cached req_* values to restore the link without re-querying firmware. Signed-off-by: Shuaisong Yang Signed-off-by: Jijie Shao Link: https://patch.msgid.link/20260624141319.271439-3-shaojijie@huawei.com Signed-off-by: Jakub Kicinski --- .../hisilicon/hns3/hns3pf/hclge_main.c | 55 ++++++++++++++----- .../hisilicon/hns3/hns3pf/hclge_main.h | 1 + 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c index 9fe6bc02d71e..fb12ba77228c 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c @@ -1504,6 +1504,11 @@ static int hclge_configure(struct hclge_dev *hdev) hdev->hw.mac.req_autoneg = AUTONEG_ENABLE; hdev->hw.mac.req_duplex = DUPLEX_FULL; + /* When lane_num is 0, the firmware will automatically + * select the appropriate lane_num based on the speed. + */ + hdev->hw.mac.req_lane_num = 0; + hclge_parse_link_mode(hdev, cfg.speed_ability); hdev->hw.mac.max_speed = hclge_get_max_speed(cfg.speed_ability); @@ -2579,6 +2584,7 @@ static int hclge_cfg_mac_speed_dup_h(struct hnae3_handle *handle, int speed, if (ret) return ret; + hdev->hw.mac.req_lane_num = lane_num; hdev->hw.mac.req_speed = (u32)speed; hdev->hw.mac.req_duplex = duplex; @@ -2884,20 +2890,6 @@ static int hclge_mac_init(struct hclge_dev *hdev) if (!test_bit(HCLGE_STATE_RST_HANDLING, &hdev->state)) hdev->hw.mac.duplex = HCLGE_MAC_FULL; - if (hdev->hw.mac.support_autoneg) { - ret = hclge_set_autoneg_en(hdev, hdev->hw.mac.autoneg); - if (ret) - return ret; - } - - if (!hdev->hw.mac.autoneg) { - ret = hclge_cfg_mac_speed_dup_hw(hdev, hdev->hw.mac.req_speed, - hdev->hw.mac.req_duplex, - hdev->hw.mac.lane_num); - if (ret) - return ret; - } - mac->link = 0; if (mac->user_fec_mode & BIT(HNAE3_FEC_USER_DEF)) { @@ -9316,6 +9308,27 @@ static int hclge_set_wol(struct hnae3_handle *handle, return ret; } +static int hclge_set_autoneg_speed_dup(struct hclge_dev *hdev) +{ + int ret; + + if (hdev->hw.mac.support_autoneg) { + ret = hclge_set_autoneg_en(hdev, hdev->hw.mac.autoneg); + if (ret) + return ret; + } + + if (!hdev->hw.mac.autoneg) { + ret = hclge_cfg_mac_speed_dup_hw(hdev, hdev->hw.mac.req_speed, + hdev->hw.mac.req_duplex, + hdev->hw.mac.req_lane_num); + if (ret) + return ret; + } + + return 0; +} + static int hclge_init_ae_dev(struct hnae3_ae_dev *ae_dev) { struct pci_dev *pdev = ae_dev->pdev; @@ -9477,6 +9490,13 @@ static int hclge_init_ae_dev(struct hnae3_ae_dev *ae_dev) if (ret) goto err_ptp_uninit; + ret = hclge_set_autoneg_speed_dup(hdev); + if (ret) { + dev_err(&pdev->dev, + "failed to set autoneg speed duplex, ret = %d\n", ret); + goto err_ptp_uninit; + } + INIT_KFIFO(hdev->mac_tnl_log); hclge_dcb_ops_set(hdev); @@ -9807,6 +9827,13 @@ static int hclge_reset_ae_dev(struct hnae3_ae_dev *ae_dev) return ret; } + ret = hclge_set_autoneg_speed_dup(hdev); + if (ret) { + dev_err(&pdev->dev, + "failed to set autoneg speed duplex, ret = %d\n", ret); + return ret; + } + ret = hclge_tp_port_init(hdev); if (ret) { dev_err(&pdev->dev, "failed to init tp port, ret = %d\n", diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h index 87adeb64e6ea..7419481422c3 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h @@ -287,6 +287,7 @@ struct hclge_mac { u8 support_autoneg; u8 speed_type; /* 0: sfp speed, 1: active speed */ u8 lane_num; + u8 req_lane_num; u32 speed; u32 req_speed; u32 max_speed; From c711f6d1cee955e04d1cd1f76cd8abd024b27a72 Mon Sep 17 00:00:00 2001 From: Shuaisong Yang Date: Wed, 24 Jun 2026 22:13:18 +0800 Subject: [PATCH 3/4] net: hns3: fix permanent link down deadlock after reset Fix a critical race condition deadlock where the network interface remains permanently Link Down after a hardware reset under specific ethtool sequences. This issue exclusively manifests in firmware-controlled PHY topologies where the driver relies on the IMP firmware to arbitrate link parameters. Standard devices driven by the kernel's native PHY_LIB are unaffected. The deadlock occurs via the following path: 1. User disables autoneg and forces an unmatched speed, forcing link down: `ethtool -s ethx autoneg off speed 10 duplex full` 2. User re-enables autoneg: `ethtool -s ethx autoneg on`. The netdev stack passes cmd->base.speed as SPEED_UNKNOWN (0xffffffff). 3. Driver saves req_autoneg=1, but before the interface can link up, a hardware reset is triggered. 4. During reset recovery, MAC init reads the un-synchronized runtime state mac.autoneg (which is still 0/OFF), misinterprets it as forced mode, and pushes the cached SPEED_UNKNOWN into the hardware registers, causing the MAC firmware state machine to freeze. Meanwhile, PHY init reads req_autoneg=1 and enables PHY autoneg. Since the MAC is frozen with 0xffffffff and PHY is running autoneg, they mismatch permanently. Fix this by: 1. Intercepting SPEED_UNKNOWN/DUPLEX_UNKNOWN in hclge_set_phy_link_ksettings() and hclge_cfg_mac_speed_dup_h() to prevent it from corrupting the driver's cached valid configuration. 2. Save req_autoneg in hclge_set_autoneg(). 3. Aligning the state judgment in hclge_set_autoneg_speed_dup() to use req_autoneg instead of the un-synchronized runtime mac.autoneg, ensuring both MAC and PHY consistently enter the autoneg branch to eliminate configuration discrepancies during reset recovery. Fixes: 05eb60e9648c ("net: hns3: using user configure after hardware reset") Signed-off-by: Shuaisong Yang Signed-off-by: Jijie Shao Link: https://patch.msgid.link/20260624141319.271439-4-shaojijie@huawei.com Signed-off-by: Jakub Kicinski --- .../hisilicon/hns3/hns3pf/hclge_main.c | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c index fb12ba77228c..d176100d3e4c 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c @@ -2585,8 +2585,10 @@ static int hclge_cfg_mac_speed_dup_h(struct hnae3_handle *handle, int speed, return ret; hdev->hw.mac.req_lane_num = lane_num; - hdev->hw.mac.req_speed = (u32)speed; - hdev->hw.mac.req_duplex = duplex; + if (speed != SPEED_UNKNOWN) + hdev->hw.mac.req_speed = (u32)speed; + if (duplex != DUPLEX_UNKNOWN) + hdev->hw.mac.req_duplex = duplex; return 0; } @@ -2617,6 +2619,7 @@ static int hclge_set_autoneg(struct hnae3_handle *handle, bool enable) { struct hclge_vport *vport = hclge_get_vport(handle); struct hclge_dev *hdev = vport->back; + int ret; if (!hdev->hw.mac.support_autoneg) { if (enable) { @@ -2628,7 +2631,10 @@ static int hclge_set_autoneg(struct hnae3_handle *handle, bool enable) } } - return hclge_set_autoneg_en(hdev, enable); + ret = hclge_set_autoneg_en(hdev, enable); + if (!ret) + hdev->hw.mac.req_autoneg = enable; + return ret; } static int hclge_get_autoneg(struct hnae3_handle *handle) @@ -3343,8 +3349,10 @@ hclge_set_phy_link_ksettings(struct hnae3_handle *handle, return ret; hdev->hw.mac.req_autoneg = cmd->base.autoneg; - hdev->hw.mac.req_speed = cmd->base.speed; - hdev->hw.mac.req_duplex = cmd->base.duplex; + if (cmd->base.speed != SPEED_UNKNOWN) + hdev->hw.mac.req_speed = cmd->base.speed; + if (cmd->base.duplex != DUPLEX_UNKNOWN) + hdev->hw.mac.req_duplex = cmd->base.duplex; return 0; } @@ -9313,12 +9321,12 @@ static int hclge_set_autoneg_speed_dup(struct hclge_dev *hdev) int ret; if (hdev->hw.mac.support_autoneg) { - ret = hclge_set_autoneg_en(hdev, hdev->hw.mac.autoneg); + ret = hclge_set_autoneg_en(hdev, hdev->hw.mac.req_autoneg); if (ret) return ret; } - if (!hdev->hw.mac.autoneg) { + if (!hdev->hw.mac.req_autoneg) { ret = hclge_cfg_mac_speed_dup_hw(hdev, hdev->hw.mac.req_speed, hdev->hw.mac.req_duplex, hdev->hw.mac.req_lane_num); From d9d349c4e8a0acd73bac8baa3605443c0df5eb26 Mon Sep 17 00:00:00 2001 From: Shuaisong Yang Date: Wed, 24 Jun 2026 22:13:19 +0800 Subject: [PATCH 4/4] net: hns3: differentiate autoneg default values between copper and fiber Fix a link loss issue during driver initialization on optical ports connected to forced-mode (non-autoneg) remote switches. Previously, during driver probe or initialization, hclge_configure() blindly hardcoded hdev->hw.mac.req_autoneg to AUTONEG_ENABLE for all media types. While this is necessary for copper (BASE-T) ports to establish a link, many high-speed optical (fiber) ports in data centers are connected to switches running in forced mode (fixed speed, autoneg disabled). Forcing autoneg on these optical ports during initialization causes a permanent link failure since the remote end refuses to respond to autoneg pulses. Fix this by implementing media-type differentiated initialization in hclge_init_ae_dev(). Copper ports continue to default to AUTONEG_ENABLE, while optical ports strictly inherit the preset autoneg status pre-configured by the firmware (hdev->hw.mac.autoneg), preserving native compatibility with forced-mode network environments. Fixes: 05eb60e9648c ("net: hns3: using user configure after hardware reset") Signed-off-by: Shuaisong Yang Signed-off-by: Jijie Shao Link: https://patch.msgid.link/20260624141319.271439-5-shaojijie@huawei.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c index d176100d3e4c..fc8587c80813 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c @@ -9498,6 +9498,13 @@ static int hclge_init_ae_dev(struct hnae3_ae_dev *ae_dev) if (ret) goto err_ptp_uninit; + if (hdev->hw.mac.media_type != HNAE3_MEDIA_TYPE_COPPER) { + hdev->hw.mac.req_autoneg = hdev->hw.mac.autoneg; + if (hdev->hw.mac.autoneg == AUTONEG_DISABLE && + hdev->hw.mac.speed != SPEED_UNKNOWN) + hdev->hw.mac.req_speed = hdev->hw.mac.speed; + } + ret = hclge_set_autoneg_speed_dup(hdev); if (ret) { dev_err(&pdev->dev,