mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2025-01-27 00:04:47 +08:00
usbnet: smsc95xx: Fix deadlock on runtime resume
[ Upstream commit7b960c967f
] Commit05b35e7eb9
("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(). Commitb4df480f68
("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:b4df480f68
("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>
This commit is contained in:
parent
eaf3a094d8
commit
b574d1e3e9
@ -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 @@ done:
|
||||
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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user