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
3 changed files with 3 additions and 3 deletions
|
@ -410,7 +410,6 @@ EXPORT_SYMBOL(phy_start_aneg);
|
||||||
|
|
||||||
|
|
||||||
static void phy_change(struct work_struct *work);
|
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
|
* phy_start_machine - start PHY state machine tracking
|
||||||
|
@ -430,7 +429,6 @@ void phy_start_machine(struct phy_device *phydev,
|
||||||
{
|
{
|
||||||
phydev->adjust_state = handler;
|
phydev->adjust_state = handler;
|
||||||
|
|
||||||
INIT_DELAYED_WORK(&phydev->state_queue, phy_state_machine);
|
|
||||||
schedule_delayed_work(&phydev->state_queue, HZ);
|
schedule_delayed_work(&phydev->state_queue, HZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -761,7 +759,7 @@ EXPORT_SYMBOL(phy_start);
|
||||||
* phy_state_machine - Handle the state machine
|
* phy_state_machine - Handle the state machine
|
||||||
* @work: work_struct that describes the work to be done
|
* @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 delayed_work *dwork = to_delayed_work(work);
|
||||||
struct phy_device *phydev =
|
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;
|
dev->state = PHY_DOWN;
|
||||||
|
|
||||||
mutex_init(&dev->lock);
|
mutex_init(&dev->lock);
|
||||||
|
INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
|
||||||
|
|
||||||
return dev;
|
return dev;
|
||||||
}
|
}
|
||||||
|
|
|
@ -485,6 +485,7 @@ void phy_driver_unregister(struct phy_driver *drv);
|
||||||
int phy_driver_register(struct phy_driver *new_driver);
|
int phy_driver_register(struct phy_driver *new_driver);
|
||||||
void phy_prepare_link(struct phy_device *phydev,
|
void phy_prepare_link(struct phy_device *phydev,
|
||||||
void (*adjust_link)(struct net_device *));
|
void (*adjust_link)(struct net_device *));
|
||||||
|
void phy_state_machine(struct work_struct *work);
|
||||||
void phy_start_machine(struct phy_device *phydev,
|
void phy_start_machine(struct phy_device *phydev,
|
||||||
void (*handler)(struct net_device *));
|
void (*handler)(struct net_device *));
|
||||||
void phy_stop_machine(struct phy_device *phydev);
|
void phy_stop_machine(struct phy_device *phydev);
|
||||||
|
|
Loading…
Reference in a new issue