Commit 35b5f6b1 authored by Nate Case's avatar Nate Case Committed by David S. Miller

PHYLIB: Locking fixes for PHY I/O potentially sleeping

PHY read/write functions can potentially sleep (e.g., a PHY accessed
via I2C).  The following changes were made to account for this:

    * Change spin locks to mutex locks
    * Add a BUG_ON() to phy_read() phy_write() to warn against
      calling them from an interrupt context.
    * Use work queue for PHY state machine handling since
      it can potentially sleep
    * Change phydev lock from spinlock to mutex
Signed-off-by: default avatarNate Case <ncase@xes-inc.com>
Acked-by: default avatarAndy Fleming <afleming@freescale.com>
Signed-off-by: default avatarJeff Garzik <jeff@garzik.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 2b912130
...@@ -49,7 +49,7 @@ int mdiobus_register(struct mii_bus *bus) ...@@ -49,7 +49,7 @@ int mdiobus_register(struct mii_bus *bus)
int i; int i;
int err = 0; int err = 0;
spin_lock_init(&bus->mdio_lock); mutex_init(&bus->mdio_lock);
if (NULL == bus || NULL == bus->name || if (NULL == bus || NULL == bus->name ||
NULL == bus->read || NULL == bus->read ||
......
...@@ -26,7 +26,6 @@ ...@@ -26,7 +26,6 @@
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/etherdevice.h> #include <linux/etherdevice.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/spinlock.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/mii.h> #include <linux/mii.h>
...@@ -72,9 +71,11 @@ int phy_read(struct phy_device *phydev, u16 regnum) ...@@ -72,9 +71,11 @@ int phy_read(struct phy_device *phydev, u16 regnum)
int retval; int retval;
struct mii_bus *bus = phydev->bus; struct mii_bus *bus = phydev->bus;
spin_lock_bh(&bus->mdio_lock); BUG_ON(in_interrupt());
mutex_lock(&bus->mdio_lock);
retval = bus->read(bus, phydev->addr, regnum); retval = bus->read(bus, phydev->addr, regnum);
spin_unlock_bh(&bus->mdio_lock); mutex_unlock(&bus->mdio_lock);
return retval; return retval;
} }
...@@ -95,9 +96,11 @@ int phy_write(struct phy_device *phydev, u16 regnum, u16 val) ...@@ -95,9 +96,11 @@ int phy_write(struct phy_device *phydev, u16 regnum, u16 val)
int err; int err;
struct mii_bus *bus = phydev->bus; struct mii_bus *bus = phydev->bus;
spin_lock_bh(&bus->mdio_lock); BUG_ON(in_interrupt());
mutex_lock(&bus->mdio_lock);
err = bus->write(bus, phydev->addr, regnum, val); err = bus->write(bus, phydev->addr, regnum, val);
spin_unlock_bh(&bus->mdio_lock); mutex_unlock(&bus->mdio_lock);
return err; return err;
} }
...@@ -428,7 +431,7 @@ int phy_start_aneg(struct phy_device *phydev) ...@@ -428,7 +431,7 @@ int phy_start_aneg(struct phy_device *phydev)
{ {
int err; int err;
spin_lock_bh(&phydev->lock); mutex_lock(&phydev->lock);
if (AUTONEG_DISABLE == phydev->autoneg) if (AUTONEG_DISABLE == phydev->autoneg)
phy_sanitize_settings(phydev); phy_sanitize_settings(phydev);
...@@ -449,13 +452,14 @@ int phy_start_aneg(struct phy_device *phydev) ...@@ -449,13 +452,14 @@ int phy_start_aneg(struct phy_device *phydev)
} }
out_unlock: out_unlock:
spin_unlock_bh(&phydev->lock); mutex_unlock(&phydev->lock);
return err; return err;
} }
EXPORT_SYMBOL(phy_start_aneg); 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);
static void phy_timer(unsigned long data); static void phy_timer(unsigned long data);
/** /**
...@@ -476,6 +480,7 @@ void phy_start_machine(struct phy_device *phydev, ...@@ -476,6 +480,7 @@ void phy_start_machine(struct phy_device *phydev,
{ {
phydev->adjust_state = handler; phydev->adjust_state = handler;
INIT_WORK(&phydev->state_queue, phy_state_machine);
init_timer(&phydev->phy_timer); init_timer(&phydev->phy_timer);
phydev->phy_timer.function = &phy_timer; phydev->phy_timer.function = &phy_timer;
phydev->phy_timer.data = (unsigned long) phydev; phydev->phy_timer.data = (unsigned long) phydev;
...@@ -493,11 +498,12 @@ void phy_start_machine(struct phy_device *phydev, ...@@ -493,11 +498,12 @@ void phy_start_machine(struct phy_device *phydev,
void phy_stop_machine(struct phy_device *phydev) void phy_stop_machine(struct phy_device *phydev)
{ {
del_timer_sync(&phydev->phy_timer); del_timer_sync(&phydev->phy_timer);
cancel_work_sync(&phydev->state_queue);
spin_lock_bh(&phydev->lock); mutex_lock(&phydev->lock);
if (phydev->state > PHY_UP) if (phydev->state > PHY_UP)
phydev->state = PHY_UP; phydev->state = PHY_UP;
spin_unlock_bh(&phydev->lock); mutex_unlock(&phydev->lock);
phydev->adjust_state = NULL; phydev->adjust_state = NULL;
} }
...@@ -541,9 +547,9 @@ static void phy_force_reduction(struct phy_device *phydev) ...@@ -541,9 +547,9 @@ static void phy_force_reduction(struct phy_device *phydev)
*/ */
void phy_error(struct phy_device *phydev) void phy_error(struct phy_device *phydev)
{ {
spin_lock_bh(&phydev->lock); mutex_lock(&phydev->lock);
phydev->state = PHY_HALTED; phydev->state = PHY_HALTED;
spin_unlock_bh(&phydev->lock); mutex_unlock(&phydev->lock);
} }
/** /**
...@@ -705,10 +711,10 @@ static void phy_change(struct work_struct *work) ...@@ -705,10 +711,10 @@ static void phy_change(struct work_struct *work)
if (err) if (err)
goto phy_err; goto phy_err;
spin_lock_bh(&phydev->lock); mutex_lock(&phydev->lock);
if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state)) if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state))
phydev->state = PHY_CHANGELINK; phydev->state = PHY_CHANGELINK;
spin_unlock_bh(&phydev->lock); mutex_unlock(&phydev->lock);
atomic_dec(&phydev->irq_disable); atomic_dec(&phydev->irq_disable);
enable_irq(phydev->irq); enable_irq(phydev->irq);
...@@ -735,7 +741,7 @@ static void phy_change(struct work_struct *work) ...@@ -735,7 +741,7 @@ static void phy_change(struct work_struct *work)
*/ */
void phy_stop(struct phy_device *phydev) void phy_stop(struct phy_device *phydev)
{ {
spin_lock_bh(&phydev->lock); mutex_lock(&phydev->lock);
if (PHY_HALTED == phydev->state) if (PHY_HALTED == phydev->state)
goto out_unlock; goto out_unlock;
...@@ -751,7 +757,7 @@ void phy_stop(struct phy_device *phydev) ...@@ -751,7 +757,7 @@ void phy_stop(struct phy_device *phydev)
phydev->state = PHY_HALTED; phydev->state = PHY_HALTED;
out_unlock: out_unlock:
spin_unlock_bh(&phydev->lock); mutex_unlock(&phydev->lock);
/* /*
* Cannot call flush_scheduled_work() here as desired because * Cannot call flush_scheduled_work() here as desired because
...@@ -773,7 +779,7 @@ void phy_stop(struct phy_device *phydev) ...@@ -773,7 +779,7 @@ void phy_stop(struct phy_device *phydev)
*/ */
void phy_start(struct phy_device *phydev) void phy_start(struct phy_device *phydev)
{ {
spin_lock_bh(&phydev->lock); mutex_lock(&phydev->lock);
switch (phydev->state) { switch (phydev->state) {
case PHY_STARTING: case PHY_STARTING:
...@@ -787,19 +793,26 @@ void phy_start(struct phy_device *phydev) ...@@ -787,19 +793,26 @@ void phy_start(struct phy_device *phydev)
default: default:
break; break;
} }
spin_unlock_bh(&phydev->lock); mutex_unlock(&phydev->lock);
} }
EXPORT_SYMBOL(phy_stop); EXPORT_SYMBOL(phy_stop);
EXPORT_SYMBOL(phy_start); EXPORT_SYMBOL(phy_start);
/* PHY timer which handles the state machine */ /**
static void phy_timer(unsigned long data) * phy_state_machine - Handle the state machine
* @work: work_struct that describes the work to be done
*
* Description: Scheduled by the state_queue workqueue each time
* phy_timer is triggered.
*/
static void phy_state_machine(struct work_struct *work)
{ {
struct phy_device *phydev = (struct phy_device *)data; struct phy_device *phydev =
container_of(work, struct phy_device, state_queue);
int needs_aneg = 0; int needs_aneg = 0;
int err = 0; int err = 0;
spin_lock_bh(&phydev->lock); mutex_lock(&phydev->lock);
if (phydev->adjust_state) if (phydev->adjust_state)
phydev->adjust_state(phydev->attached_dev); phydev->adjust_state(phydev->attached_dev);
...@@ -965,7 +978,7 @@ static void phy_timer(unsigned long data) ...@@ -965,7 +978,7 @@ static void phy_timer(unsigned long data)
break; break;
} }
spin_unlock_bh(&phydev->lock); mutex_unlock(&phydev->lock);
if (needs_aneg) if (needs_aneg)
err = phy_start_aneg(phydev); err = phy_start_aneg(phydev);
...@@ -976,3 +989,14 @@ static void phy_timer(unsigned long data) ...@@ -976,3 +989,14 @@ static void phy_timer(unsigned long data)
mod_timer(&phydev->phy_timer, jiffies + PHY_STATE_TIME * HZ); mod_timer(&phydev->phy_timer, jiffies + PHY_STATE_TIME * HZ);
} }
/* PHY timer which schedules the state machine work */
static void phy_timer(unsigned long data)
{
struct phy_device *phydev = (struct phy_device *)data;
/*
* PHY I/O operations can potentially sleep so we ensure that
* it's done from a process context
*/
schedule_work(&phydev->state_queue);
}
...@@ -25,7 +25,6 @@ ...@@ -25,7 +25,6 @@
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/etherdevice.h> #include <linux/etherdevice.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/spinlock.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/mii.h> #include <linux/mii.h>
...@@ -80,7 +79,7 @@ struct phy_device* phy_device_create(struct mii_bus *bus, int addr, int phy_id) ...@@ -80,7 +79,7 @@ struct phy_device* phy_device_create(struct mii_bus *bus, int addr, int phy_id)
dev->state = PHY_DOWN; dev->state = PHY_DOWN;
spin_lock_init(&dev->lock); mutex_init(&dev->lock);
return dev; return dev;
} }
...@@ -656,7 +655,7 @@ static int phy_probe(struct device *dev) ...@@ -656,7 +655,7 @@ static int phy_probe(struct device *dev)
if (!(phydrv->flags & PHY_HAS_INTERRUPT)) if (!(phydrv->flags & PHY_HAS_INTERRUPT))
phydev->irq = PHY_POLL; phydev->irq = PHY_POLL;
spin_lock_bh(&phydev->lock); mutex_lock(&phydev->lock);
/* Start out supporting everything. Eventually, /* Start out supporting everything. Eventually,
* a controller will attach, and may modify one * a controller will attach, and may modify one
...@@ -670,7 +669,7 @@ static int phy_probe(struct device *dev) ...@@ -670,7 +669,7 @@ static int phy_probe(struct device *dev)
if (phydev->drv->probe) if (phydev->drv->probe)
err = phydev->drv->probe(phydev); err = phydev->drv->probe(phydev);
spin_unlock_bh(&phydev->lock); mutex_unlock(&phydev->lock);
return err; return err;
...@@ -682,9 +681,9 @@ static int phy_remove(struct device *dev) ...@@ -682,9 +681,9 @@ static int phy_remove(struct device *dev)
phydev = to_phy_device(dev); phydev = to_phy_device(dev);
spin_lock_bh(&phydev->lock); mutex_lock(&phydev->lock);
phydev->state = PHY_DOWN; phydev->state = PHY_DOWN;
spin_unlock_bh(&phydev->lock); mutex_unlock(&phydev->lock);
if (phydev->drv->remove) if (phydev->drv->remove)
phydev->drv->remove(phydev); phydev->drv->remove(phydev);
......
...@@ -88,7 +88,7 @@ struct mii_bus { ...@@ -88,7 +88,7 @@ struct mii_bus {
/* A lock to ensure that only one thing can read/write /* A lock to ensure that only one thing can read/write
* the MDIO bus at a time */ * the MDIO bus at a time */
spinlock_t mdio_lock; struct mutex mdio_lock;
struct device *dev; struct device *dev;
...@@ -284,10 +284,11 @@ struct phy_device { ...@@ -284,10 +284,11 @@ struct phy_device {
/* Interrupt and Polling infrastructure */ /* Interrupt and Polling infrastructure */
struct work_struct phy_queue; struct work_struct phy_queue;
struct work_struct state_queue;
struct timer_list phy_timer; struct timer_list phy_timer;
atomic_t irq_disable; atomic_t irq_disable;
spinlock_t lock; struct mutex lock;
struct net_device *attached_dev; struct net_device *attached_dev;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment