usbnet: smsc95xx: Fix deadlock on runtime resume
commit 7b960c967f2aa01ab8f45c5a0bd78e754cffdeee upstream. 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: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
db3b738ae5
commit
f924211946
@ -71,6 +71,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;
|
||||
@ -80,13 +81,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;
|
||||
@ -110,13 +112,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;
|
||||
@ -1490,9 +1493,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;
|
||||
}
|
||||
|
||||
@ -1732,6 +1738,7 @@ done:
|
||||
if (ret && PMSG_IS_AUTO(message))
|
||||
usbnet_resume(intf);
|
||||
|
||||
pdata->pm_task = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -1752,29 +1759,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);
|
||||
@ -1783,15 +1792,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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user