mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/
synced 2025-04-19 20:58:31 +09:00

After detecting the np_link_fail exception, the driver attempts to fix the exception by using phy_stop() and phy_start() in the scheduled task. However, hbg_fix_np_link_fail() and .ndo_stop() may be concurrently executed. As a result, phy_stop() is executed twice, and the following Calltrace occurs: hibmcge 0000:84:00.2 enp132s0f2: Link is Down hibmcge 0000:84:00.2: failed to link between MAC and PHY, try to fix... ------------[ cut here ]------------ called from state HALTED WARNING: CPU: 71 PID: 23391 at drivers/net/phy/phy.c:1503 phy_stop... ... pc : phy_stop+0x138/0x180 lr : phy_stop+0x138/0x180 sp : ffff8000c76bbd40 x29: ffff8000c76bbd40 x28: 0000000000000000 x27: 0000000000000000 x26: ffff2020047358c0 x25: ffff202004735940 x24: ffff20200000e405 x23: ffff2020060e5178 x22: ffff2020060e4000 x21: ffff2020060e49c0 x20: ffff2020060e5170 x19: ffff20202538e000 x18: 0000000000000020 x17: 0000000000000000 x16: ffffcede02e28f40 x15: ffffffffffffffff x14: 0000000000000000 x13: 205d313933333254 x12: 5b5d393430303233 x11: ffffcede04555958 x10: ffffcede04495918 x9 : ffffcede0274fee0 x8 : 00000000000bffe8 x7 : c0000000ffff7fff x6 : 0000000000000001 x5 : 00000000002bffa8 x4 : 0000000000000000 x3 : 0000000000000000 x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffff20202e429480 Call trace: phy_stop+0x138/0x180 hbg_fix_np_link_fail+0x4c/0x90 [hibmcge] hbg_service_task+0xfc/0x148 [hibmcge] process_one_work+0x180/0x398 worker_thread+0x210/0x328 kthread+0xe0/0xf0 ret_from_fork+0x10/0x20 ---[ end trace 0000000000000000 ]--- This patch adds the rtnl_lock to hbg_fix_np_link_fail() to ensure that other operations are not performed concurrently. In addition, np_link_fail exception can be fixed only when the PHY is link. Fixes: e0306637e85d ("net: hibmcge: Add support for mac link exception handling feature") Signed-off-by: Jijie Shao <shaojijie@huawei.com> Reviewed-by: Simon Horman <horms@kernel.org> Link: https://patch.msgid.link/20250410021327.590362-8-shaojijie@huawei.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
269 lines
6.6 KiB
C
269 lines
6.6 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
// Copyright (c) 2024 Hisilicon Limited.
|
|
|
|
#include <linux/phy.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include "hbg_common.h"
|
|
#include "hbg_hw.h"
|
|
#include "hbg_mdio.h"
|
|
#include "hbg_reg.h"
|
|
|
|
#define HBG_MAC_GET_PRIV(mac) ((struct hbg_priv *)(mac)->mdio_bus->priv)
|
|
#define HBG_MII_BUS_GET_MAC(bus) (&((struct hbg_priv *)(bus)->priv)->mac)
|
|
|
|
#define HBG_MDIO_C22_MODE 0x1
|
|
#define HBG_MDIO_C22_REG_WRITE 0x1
|
|
#define HBG_MDIO_C22_REG_READ 0x2
|
|
|
|
#define HBG_MDIO_OP_TIMEOUT_US (1 * 1000 * 1000)
|
|
#define HBG_MDIO_OP_INTERVAL_US (5 * 1000)
|
|
|
|
#define HBG_NP_LINK_FAIL_RETRY_TIMES 5
|
|
|
|
static void hbg_mdio_set_command(struct hbg_mac *mac, u32 cmd)
|
|
{
|
|
hbg_reg_write(HBG_MAC_GET_PRIV(mac), HBG_REG_MDIO_COMMAND_ADDR, cmd);
|
|
}
|
|
|
|
static void hbg_mdio_get_command(struct hbg_mac *mac, u32 *cmd)
|
|
{
|
|
*cmd = hbg_reg_read(HBG_MAC_GET_PRIV(mac), HBG_REG_MDIO_COMMAND_ADDR);
|
|
}
|
|
|
|
static void hbg_mdio_set_wdata_reg(struct hbg_mac *mac, u16 wdata_value)
|
|
{
|
|
hbg_reg_write_field(HBG_MAC_GET_PRIV(mac), HBG_REG_MDIO_WDATA_ADDR,
|
|
HBG_REG_MDIO_WDATA_M, wdata_value);
|
|
}
|
|
|
|
static u32 hbg_mdio_get_rdata_reg(struct hbg_mac *mac)
|
|
{
|
|
return hbg_reg_read_field(HBG_MAC_GET_PRIV(mac),
|
|
HBG_REG_MDIO_RDATA_ADDR,
|
|
HBG_REG_MDIO_WDATA_M);
|
|
}
|
|
|
|
static int hbg_mdio_wait_ready(struct hbg_mac *mac)
|
|
{
|
|
struct hbg_priv *priv = HBG_MAC_GET_PRIV(mac);
|
|
u32 cmd = 0;
|
|
int ret;
|
|
|
|
ret = readl_poll_timeout(priv->io_base + HBG_REG_MDIO_COMMAND_ADDR, cmd,
|
|
!FIELD_GET(HBG_REG_MDIO_COMMAND_START_B, cmd),
|
|
HBG_MDIO_OP_INTERVAL_US,
|
|
HBG_MDIO_OP_TIMEOUT_US);
|
|
|
|
return ret ? -ETIMEDOUT : 0;
|
|
}
|
|
|
|
static int hbg_mdio_cmd_send(struct hbg_mac *mac, u32 prt_addr, u32 dev_addr,
|
|
u32 type, u32 op_code)
|
|
{
|
|
u32 cmd = 0;
|
|
|
|
hbg_mdio_get_command(mac, &cmd);
|
|
hbg_field_modify(cmd, HBG_REG_MDIO_COMMAND_ST_M, type);
|
|
hbg_field_modify(cmd, HBG_REG_MDIO_COMMAND_OP_M, op_code);
|
|
hbg_field_modify(cmd, HBG_REG_MDIO_COMMAND_PRTAD_M, prt_addr);
|
|
hbg_field_modify(cmd, HBG_REG_MDIO_COMMAND_DEVAD_M, dev_addr);
|
|
|
|
/* if auto scan enabled, this value need fix to 0 */
|
|
hbg_field_modify(cmd, HBG_REG_MDIO_COMMAND_START_B, 0x1);
|
|
|
|
hbg_mdio_set_command(mac, cmd);
|
|
|
|
/* wait operation complete and check the result */
|
|
return hbg_mdio_wait_ready(mac);
|
|
}
|
|
|
|
static int hbg_mdio_read22(struct mii_bus *bus, int phy_addr, int regnum)
|
|
{
|
|
struct hbg_mac *mac = HBG_MII_BUS_GET_MAC(bus);
|
|
int ret;
|
|
|
|
ret = hbg_mdio_cmd_send(mac, phy_addr, regnum, HBG_MDIO_C22_MODE,
|
|
HBG_MDIO_C22_REG_READ);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return hbg_mdio_get_rdata_reg(mac);
|
|
}
|
|
|
|
static int hbg_mdio_write22(struct mii_bus *bus, int phy_addr, int regnum,
|
|
u16 val)
|
|
{
|
|
struct hbg_mac *mac = HBG_MII_BUS_GET_MAC(bus);
|
|
|
|
hbg_mdio_set_wdata_reg(mac, val);
|
|
return hbg_mdio_cmd_send(mac, phy_addr, regnum, HBG_MDIO_C22_MODE,
|
|
HBG_MDIO_C22_REG_WRITE);
|
|
}
|
|
|
|
static void hbg_mdio_init_hw(struct hbg_priv *priv)
|
|
{
|
|
u32 freq = priv->dev_specs.mdio_frequency;
|
|
struct hbg_mac *mac = &priv->mac;
|
|
u32 cmd = 0;
|
|
|
|
cmd |= FIELD_PREP(HBG_REG_MDIO_COMMAND_ST_M, HBG_MDIO_C22_MODE);
|
|
cmd |= FIELD_PREP(HBG_REG_MDIO_COMMAND_AUTO_SCAN_B, HBG_STATUS_DISABLE);
|
|
|
|
/* freq use two bits, which are stored in clk_sel and clk_sel_exp */
|
|
cmd |= FIELD_PREP(HBG_REG_MDIO_COMMAND_CLK_SEL_B, freq & 0x1);
|
|
cmd |= FIELD_PREP(HBG_REG_MDIO_COMMAND_CLK_SEL_EXP_B,
|
|
(freq >> 1) & 0x1);
|
|
|
|
hbg_mdio_set_command(mac, cmd);
|
|
}
|
|
|
|
static void hbg_flowctrl_cfg(struct hbg_priv *priv)
|
|
{
|
|
struct phy_device *phydev = priv->mac.phydev;
|
|
bool rx_pause;
|
|
bool tx_pause;
|
|
|
|
if (!priv->mac.pause_autoneg)
|
|
return;
|
|
|
|
phy_get_pause(phydev, &tx_pause, &rx_pause);
|
|
hbg_hw_set_pause_enable(priv, tx_pause, rx_pause);
|
|
}
|
|
|
|
void hbg_fix_np_link_fail(struct hbg_priv *priv)
|
|
{
|
|
struct device *dev = &priv->pdev->dev;
|
|
|
|
rtnl_lock();
|
|
|
|
if (priv->stats.np_link_fail_cnt >= HBG_NP_LINK_FAIL_RETRY_TIMES) {
|
|
dev_err(dev, "failed to fix the MAC link status\n");
|
|
priv->stats.np_link_fail_cnt = 0;
|
|
goto unlock;
|
|
}
|
|
|
|
if (!priv->mac.phydev->link)
|
|
goto unlock;
|
|
|
|
priv->stats.np_link_fail_cnt++;
|
|
dev_err(dev, "failed to link between MAC and PHY, try to fix...\n");
|
|
|
|
/* Replace phy_reset() with phy_stop() and phy_start(),
|
|
* as suggested by Andrew.
|
|
*/
|
|
hbg_phy_stop(priv);
|
|
hbg_phy_start(priv);
|
|
|
|
unlock:
|
|
rtnl_unlock();
|
|
}
|
|
|
|
static void hbg_phy_adjust_link(struct net_device *netdev)
|
|
{
|
|
struct hbg_priv *priv = netdev_priv(netdev);
|
|
struct phy_device *phydev = netdev->phydev;
|
|
u32 speed;
|
|
|
|
if (phydev->link != priv->mac.link_status) {
|
|
if (phydev->link) {
|
|
switch (phydev->speed) {
|
|
case SPEED_10:
|
|
speed = HBG_PORT_MODE_SGMII_10M;
|
|
break;
|
|
case SPEED_100:
|
|
speed = HBG_PORT_MODE_SGMII_100M;
|
|
break;
|
|
case SPEED_1000:
|
|
speed = HBG_PORT_MODE_SGMII_1000M;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
priv->mac.speed = speed;
|
|
priv->mac.duplex = phydev->duplex;
|
|
priv->mac.autoneg = phydev->autoneg;
|
|
hbg_hw_adjust_link(priv, speed, phydev->duplex);
|
|
hbg_flowctrl_cfg(priv);
|
|
}
|
|
|
|
priv->mac.link_status = phydev->link;
|
|
phy_print_status(phydev);
|
|
}
|
|
}
|
|
|
|
static void hbg_phy_disconnect(void *data)
|
|
{
|
|
phy_disconnect((struct phy_device *)data);
|
|
}
|
|
|
|
static int hbg_phy_connect(struct hbg_priv *priv)
|
|
{
|
|
struct phy_device *phydev = priv->mac.phydev;
|
|
struct device *dev = &priv->pdev->dev;
|
|
int ret;
|
|
|
|
ret = phy_connect_direct(priv->netdev, phydev, hbg_phy_adjust_link,
|
|
PHY_INTERFACE_MODE_SGMII);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "failed to connect phy\n");
|
|
|
|
ret = devm_add_action_or_reset(dev, hbg_phy_disconnect, phydev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_1000baseT_Half_BIT);
|
|
phy_support_asym_pause(phydev);
|
|
phy_attached_info(phydev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void hbg_phy_start(struct hbg_priv *priv)
|
|
{
|
|
phy_start(priv->mac.phydev);
|
|
}
|
|
|
|
void hbg_phy_stop(struct hbg_priv *priv)
|
|
{
|
|
phy_stop(priv->mac.phydev);
|
|
}
|
|
|
|
int hbg_mdio_init(struct hbg_priv *priv)
|
|
{
|
|
struct device *dev = &priv->pdev->dev;
|
|
struct hbg_mac *mac = &priv->mac;
|
|
struct phy_device *phydev;
|
|
struct mii_bus *mdio_bus;
|
|
int ret;
|
|
|
|
mac->phy_addr = priv->dev_specs.phy_addr;
|
|
mdio_bus = devm_mdiobus_alloc(dev);
|
|
if (!mdio_bus)
|
|
return dev_err_probe(dev, -ENOMEM,
|
|
"failed to alloc MDIO bus\n");
|
|
|
|
mdio_bus->parent = dev;
|
|
mdio_bus->priv = priv;
|
|
mdio_bus->phy_mask = ~(1 << mac->phy_addr);
|
|
mdio_bus->name = "hibmcge mii bus";
|
|
mac->mdio_bus = mdio_bus;
|
|
|
|
mdio_bus->read = hbg_mdio_read22;
|
|
mdio_bus->write = hbg_mdio_write22;
|
|
snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s-%s", "mii", dev_name(dev));
|
|
|
|
ret = devm_mdiobus_register(dev, mdio_bus);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "failed to register MDIO bus\n");
|
|
|
|
phydev = mdiobus_get_phy(mdio_bus, mac->phy_addr);
|
|
if (!phydev)
|
|
return dev_err_probe(dev, -ENODEV,
|
|
"failed to get phy device\n");
|
|
|
|
mac->phydev = phydev;
|
|
hbg_mdio_init_hw(priv);
|
|
return hbg_phy_connect(priv);
|
|
}
|