]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
net: hns3: fix permanent link down deadlock after reset
authorShuaisong Yang <yangshuaisong@h-partners.com>
Wed, 24 Jun 2026 14:13:18 +0000 (22:13 +0800)
committerJakub Kicinski <kuba@kernel.org>
Thu, 25 Jun 2026 16:15:44 +0000 (09:15 -0700)
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 <yangshuaisong@h-partners.com>
Signed-off-by: Jijie Shao <shaojijie@huawei.com>
Link: https://patch.msgid.link/20260624141319.271439-4-shaojijie@huawei.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c

index fb12ba77228c95029f0fe657c69001199f4c84d6..d176100d3e4c8f863338c5a68b271b967298c638 100644 (file)
@@ -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);