lkml.org 
[lkml]   [2022]   [Aug]   [15]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 5.15 698/779] usbnet: smsc95xx: Fix deadlock on runtime resume
    Date
    From: Lukas Wunner <lukas@wunner.de>

    [ Upstream commit 7b960c967f2aa01ab8f45c5a0bd78e754cffdeee ]

    Commit 05b35e7eb9a1 ("smsc95xx: add phylib support") amended
    smsc95xx_resume() to call phy_init_hw(). That function waits for the
    device to runtime resume even though it is placed in the runtime resume
    path, causing a deadlock.

    The problem is that phy_init_hw() calls down to smsc95xx_mdiobus_read(),
    which never uses the _nopm variant of usbnet_read_cmd().

    Commit b4df480f68ae ("usbnet: smsc95xx: add reset_resume function with
    reset operation") causes a similar deadlock on resume if the device was
    already runtime suspended when entering system sleep:

    That's because the commit introduced smsc95xx_reset_resume(), which
    calls down to smsc95xx_reset(), which neglects to use _nopm accessors.

    Fix by auto-detecting whether a device access is performed by the
    suspend/resume task_struct and use the _nopm variant if so. This works
    because the PM core guarantees that suspend/resume callbacks are run in
    task context.

    Stacktrace for posterity:

    INFO: task kworker/2:1:49 blocked for more than 122 seconds.
    Workqueue: usb_hub_wq hub_event
    schedule
    rpm_resume
    __pm_runtime_resume
    usb_autopm_get_interface
    usbnet_read_cmd
    __smsc95xx_read_reg
    __smsc95xx_phy_wait_not_busy
    __smsc95xx_mdio_read
    smsc95xx_mdiobus_read
    __mdiobus_read
    mdiobus_read
    smsc_phy_reset
    phy_init_hw
    smsc95xx_resume
    usb_resume_interface
    usb_resume_both
    usb_runtime_resume
    __rpm_callback
    rpm_callback
    rpm_resume
    __pm_runtime_resume
    usb_autoresume_device
    hub_event
    process_one_work

    Fixes: b4df480f68ae ("usbnet: smsc95xx: add reset_resume function with reset operation")
    Signed-off-by: Lukas Wunner <lukas@wunner.de>
    Cc: stable@vger.kernel.org # v3.16+
    Cc: Andre Edich <andre.edich@microchip.com>
    Signed-off-by: David S. Miller <davem@davemloft.net>
    Signed-off-by: Sasha Levin <sashal@kernel.org>
    ---
    drivers/net/usb/smsc95xx.c | 26 ++++++++++++++++++++------
    1 file changed, 20 insertions(+), 6 deletions(-)

    diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c
    index 3eb62197c2f5..460e90eb528f 100644
    --- a/drivers/net/usb/smsc95xx.c
    +++ b/drivers/net/usb/smsc95xx.c
    @@ -69,6 +69,7 @@ struct smsc95xx_priv {
    struct fwnode_handle *irqfwnode;
    struct mii_bus *mdiobus;
    struct phy_device *phydev;
    + struct task_struct *pm_task;
    };

    static bool turbo_mode = true;
    @@ -78,13 +79,14 @@ MODULE_PARM_DESC(turbo_mode, "Enable multiple frames per Rx transaction");
    static int __must_check __smsc95xx_read_reg(struct usbnet *dev, u32 index,
    u32 *data, int in_pm)
    {
    + struct smsc95xx_priv *pdata = dev->driver_priv;
    u32 buf;
    int ret;
    int (*fn)(struct usbnet *, u8, u8, u16, u16, void *, u16);

    BUG_ON(!dev);

    - if (!in_pm)
    + if (current != pdata->pm_task)
    fn = usbnet_read_cmd;
    else
    fn = usbnet_read_cmd_nopm;
    @@ -108,13 +110,14 @@ static int __must_check __smsc95xx_read_reg(struct usbnet *dev, u32 index,
    static int __must_check __smsc95xx_write_reg(struct usbnet *dev, u32 index,
    u32 data, int in_pm)
    {
    + struct smsc95xx_priv *pdata = dev->driver_priv;
    u32 buf;
    int ret;
    int (*fn)(struct usbnet *, u8, u8, u16, u16, const void *, u16);

    BUG_ON(!dev);

    - if (!in_pm)
    + if (current != pdata->pm_task)
    fn = usbnet_write_cmd;
    else
    fn = usbnet_write_cmd_nopm;
    @@ -1482,9 +1485,12 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message)
    u32 val, link_up;
    int ret;

    + pdata->pm_task = current;
    +
    ret = usbnet_suspend(intf, message);
    if (ret < 0) {
    netdev_warn(dev->net, "usbnet_suspend error\n");
    + pdata->pm_task = NULL;
    return ret;
    }

    @@ -1724,6 +1730,7 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message)
    if (ret && PMSG_IS_AUTO(message))
    usbnet_resume(intf);

    + pdata->pm_task = NULL;
    return ret;
    }

    @@ -1744,29 +1751,31 @@ static int smsc95xx_resume(struct usb_interface *intf)
    /* do this first to ensure it's cleared even in error case */
    pdata->suspend_flags = 0;

    + pdata->pm_task = current;
    +
    if (suspend_flags & SUSPEND_ALLMODES) {
    /* clear wake-up sources */
    ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val);
    if (ret < 0)
    - return ret;
    + goto done;

    val &= ~(WUCSR_WAKE_EN_ | WUCSR_MPEN_);

    ret = smsc95xx_write_reg_nopm(dev, WUCSR, val);
    if (ret < 0)
    - return ret;
    + goto done;

    /* clear wake-up status */
    ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val);
    if (ret < 0)
    - return ret;
    + goto done;

    val &= ~PM_CTL_WOL_EN_;
    val |= PM_CTL_WUPS_;

    ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val);
    if (ret < 0)
    - return ret;
    + goto done;
    }

    phy_init_hw(pdata->phydev);
    @@ -1775,15 +1784,20 @@ static int smsc95xx_resume(struct usb_interface *intf)
    if (ret < 0)
    netdev_warn(dev->net, "usbnet_resume error\n");

    +done:
    + pdata->pm_task = NULL;
    return ret;
    }

    static int smsc95xx_reset_resume(struct usb_interface *intf)
    {
    struct usbnet *dev = usb_get_intfdata(intf);
    + struct smsc95xx_priv *pdata = dev->driver_priv;
    int ret;

    + pdata->pm_task = current;
    ret = smsc95xx_reset(dev);
    + pdata->pm_task = NULL;
    if (ret < 0)
    return ret;

    --
    2.35.1


    \
     
     \ /
      Last update: 2022-08-15 21:55    [W:3.956 / U:1.700 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site