phylib: Move workqueue initialization to a proper place
commit 541cd3ee00
("phylib: Fix deadlock
on resume") caused TI DaVinci EMAC ethernet driver to oops upon resume:
PM: resume of devices complete after 237.098 msecs
Restarting tasks ... done.
kernel BUG at kernel/workqueue.c:354!
Unable to handle kernel NULL pointer dereference at virtual address 00000000
[...]
Backtrace:
[<c002c598>] (__bug+0x0/0x2c) from [<c0052a54>] (queue_delayed_work_on+0x74/0xf8)
[<c00529e0>] (queue_delayed_work_on+0x0/0xf8) from [<c0052b30>] (queue_delayed_work+0x2c/0x30)
The oops pops up because TI DaVinci EMAC driver detaches PHY on
suspend and attaches it back on resume. Attaching makes phylib call
phy_start_machine() that initializes a workqueue. On the other hand,
PHY's resume routine will call phy_start_machine() again, and that
will cause the oops since we just destroyed the already scheduled
workqueue.
This patch fixes the issue by moving workqueue initialization to
phy_device_create().
p.s. We don't see this oops with ucc_geth and gianfar drivers because
they perform a fine-grained suspend, i.e. they just stop the PHYs
without detaching.
Reported-by: Sekhar Nori <nsekhar@ti.com>
Signed-off-by: Anton Vorontsov <avorontsov@ru.mvista.com>
Tested-by: Sekhar Nori <nsekhar@ti.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
de4ef86cfc
commit
4f9c85a1b0
|
@ -410,7 +410,6 @@ EXPORT_SYMBOL(phy_start_aneg);
|
|||
|
||||
|
||||
static void phy_change(struct work_struct *work);
|
||||
static void phy_state_machine(struct work_struct *work);
|
||||
|
||||
/**
|
||||
* phy_start_machine - start PHY state machine tracking
|
||||
|
@ -430,7 +429,6 @@ void phy_start_machine(struct phy_device *phydev,
|
|||
{
|
||||
phydev->adjust_state = handler;
|
||||
|
||||
INIT_DELAYED_WORK(&phydev->state_queue, phy_state_machine);
|
||||
schedule_delayed_work(&phydev->state_queue, HZ);
|
||||
}
|
||||
|
||||
|
@ -761,7 +759,7 @@ EXPORT_SYMBOL(phy_start);
|
|||
* phy_state_machine - Handle the state machine
|
||||
* @work: work_struct that describes the work to be done
|
||||
*/
|
||||
static void phy_state_machine(struct work_struct *work)
|
||||
void phy_state_machine(struct work_struct *work)
|
||||
{
|
||||
struct delayed_work *dwork = to_delayed_work(work);
|
||||
struct phy_device *phydev =
|
||||
|
|
|
@ -177,6 +177,7 @@ struct phy_device* phy_device_create(struct mii_bus *bus, int addr, int phy_id)
|
|||
dev->state = PHY_DOWN;
|
||||
|
||||
mutex_init(&dev->lock);
|
||||
INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
|
|
@ -485,6 +485,7 @@ void phy_driver_unregister(struct phy_driver *drv);
|
|||
int phy_driver_register(struct phy_driver *new_driver);
|
||||
void phy_prepare_link(struct phy_device *phydev,
|
||||
void (*adjust_link)(struct net_device *));
|
||||
void phy_state_machine(struct work_struct *work);
|
||||
void phy_start_machine(struct phy_device *phydev,
|
||||
void (*handler)(struct net_device *));
|
||||
void phy_stop_machine(struct phy_device *phydev);
|
||||
|
|
Loading…
Reference in a new issue