Commit 318ff60e authored by David S. Miller's avatar David S. Miller

Merge branch 'net-Resolve-races-in-phy-accessors'

Russell King says:

====================
Resolve races in phy accessors

This series resolves races with various accesses to PHY registers.
The first five patches are necessary before we add phylink support
to mvneta, the remaining three are merely cleanups for unobserved
races, and hence are less critical.

There are two possible classes of races that can occur: where we
write to a page register that changes the meaning of a group of
other registers, and where we read-modify-write a register.

Resolve these races by performing the accesses under the mdio bus
lock, ensuring that no other user can access the bus while the
series of atomic operations are being performed.

These patches have been posted before, and have been modified
along the lines of previous feedback:

- The third patch was originally reviewed by Florian, but as I've
  added __phy_modify() to it, I've removed that attributation.
- Included generic page-based accessors as suggested last time
  around.
- Since we have the unlocked __phy_modify() in this patch series,
  it is sensible to include the changes for this to marvell.c -
  these accessors have to change anyway to avoid deadlocks on the
  mdio bus lock.

I haven't been able to test the at803x.c changes yet beyond compile
testing - although I do have systems with an ar8035 PHY.  However,
they should be straight forward to review.

This is targetted for net-next because the races have not been
found in existing drivers, but have been observed with phylink
integrated into mvneta - that's not to say that the races do not
exist today, they are just unobserved (probably through lack of
rigorous enough testing.)  The race provoking condition is detailed
in patch 5.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents be6e36d9 fea23fb5
......@@ -215,34 +215,22 @@ static int at803x_suspend(struct phy_device *phydev)
int value;
int wol_enabled;
mutex_lock(&phydev->lock);
value = phy_read(phydev, AT803X_INTR_ENABLE);
wol_enabled = value & AT803X_INTR_ENABLE_WOL;
value = phy_read(phydev, MII_BMCR);
if (wol_enabled)
value |= BMCR_ISOLATE;
value = BMCR_ISOLATE;
else
value |= BMCR_PDOWN;
value = BMCR_PDOWN;
phy_write(phydev, MII_BMCR, value);
mutex_unlock(&phydev->lock);
phy_modify(phydev, MII_BMCR, 0, value);
return 0;
}
static int at803x_resume(struct phy_device *phydev)
{
int value;
value = phy_read(phydev, MII_BMCR);
value &= ~(BMCR_PDOWN | BMCR_ISOLATE);
phy_write(phydev, MII_BMCR, value);
return 0;
return phy_modify(phydev, MII_BMCR, ~(BMCR_PDOWN | BMCR_ISOLATE), 0);
}
static int at803x_probe(struct phy_device *phydev)
......
This diff is collapsed.
......@@ -514,6 +514,55 @@ struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr)
}
EXPORT_SYMBOL(mdiobus_scan);
/**
* __mdiobus_read - Unlocked version of the mdiobus_read function
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to read
*
* Read a MDIO bus register. Caller must hold the mdio bus lock.
*
* NOTE: MUST NOT be called from interrupt context.
*/
int __mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
{
int retval;
WARN_ON_ONCE(!mutex_is_locked(&bus->mdio_lock));
retval = bus->read(bus, addr, regnum);
trace_mdio_access(bus, 1, addr, regnum, retval, retval);
return retval;
}
EXPORT_SYMBOL(__mdiobus_read);
/**
* __mdiobus_write - Unlocked version of the mdiobus_write function
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to write
* @val: value to write to @regnum
*
* Write a MDIO bus register. Caller must hold the mdio bus lock.
*
* NOTE: MUST NOT be called from interrupt context.
*/
int __mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
{
int err;
WARN_ON_ONCE(!mutex_is_locked(&bus->mdio_lock));
err = bus->write(bus, addr, regnum, val);
trace_mdio_access(bus, 0, addr, regnum, val, err);
return err;
}
EXPORT_SYMBOL(__mdiobus_write);
/**
* mdiobus_read_nested - Nested version of the mdiobus_read function
* @bus: the mii_bus struct
......@@ -534,11 +583,9 @@ int mdiobus_read_nested(struct mii_bus *bus, int addr, u32 regnum)
BUG_ON(in_interrupt());
mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
retval = bus->read(bus, addr, regnum);
retval = __mdiobus_read(bus, addr, regnum);
mutex_unlock(&bus->mdio_lock);
trace_mdio_access(bus, 1, addr, regnum, retval, retval);
return retval;
}
EXPORT_SYMBOL(mdiobus_read_nested);
......@@ -560,11 +607,9 @@ int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
BUG_ON(in_interrupt());
mutex_lock(&bus->mdio_lock);
retval = bus->read(bus, addr, regnum);
retval = __mdiobus_read(bus, addr, regnum);
mutex_unlock(&bus->mdio_lock);
trace_mdio_access(bus, 1, addr, regnum, retval, retval);
return retval;
}
EXPORT_SYMBOL(mdiobus_read);
......@@ -590,11 +635,9 @@ int mdiobus_write_nested(struct mii_bus *bus, int addr, u32 regnum, u16 val)
BUG_ON(in_interrupt());
mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
err = bus->write(bus, addr, regnum, val);
err = __mdiobus_write(bus, addr, regnum, val);
mutex_unlock(&bus->mdio_lock);
trace_mdio_access(bus, 0, addr, regnum, val, err);
return err;
}
EXPORT_SYMBOL(mdiobus_write_nested);
......@@ -617,11 +660,9 @@ int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
BUG_ON(in_interrupt());
mutex_lock(&bus->mdio_lock);
err = bus->write(bus, addr, regnum, val);
err = __mdiobus_write(bus, addr, regnum, val);
mutex_unlock(&bus->mdio_lock);
trace_mdio_access(bus, 0, addr, regnum, val, err);
return err;
}
EXPORT_SYMBOL(mdiobus_write);
......
......@@ -236,13 +236,14 @@ static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad,
u16 regnum)
{
/* Write the desired MMD Devad */
bus->write(bus, phy_addr, MII_MMD_CTRL, devad);
__mdiobus_write(bus, phy_addr, MII_MMD_CTRL, devad);
/* Write the desired MMD register address */
bus->write(bus, phy_addr, MII_MMD_DATA, regnum);
__mdiobus_write(bus, phy_addr, MII_MMD_DATA, regnum);
/* Select the Function : DATA with no post increment */
bus->write(bus, phy_addr, MII_MMD_CTRL, devad | MII_MMD_CTRL_NOINCR);
__mdiobus_write(bus, phy_addr, MII_MMD_CTRL,
devad | MII_MMD_CTRL_NOINCR);
}
/**
......@@ -275,7 +276,7 @@ int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum)
mmd_phy_indirect(bus, phy_addr, devad, regnum);
/* Read the content of the MMD's selected register */
val = bus->read(bus, phy_addr, MII_MMD_DATA);
val = __mdiobus_read(bus, phy_addr, MII_MMD_DATA);
mutex_unlock(&bus->mdio_lock);
}
return val;
......@@ -314,7 +315,7 @@ int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val)
mmd_phy_indirect(bus, phy_addr, devad, regnum);
/* Write the data into MMD's selected register */
bus->write(bus, phy_addr, MII_MMD_DATA, val);
__mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);
mutex_unlock(&bus->mdio_lock);
ret = 0;
......@@ -322,3 +323,208 @@ int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val)
return ret;
}
EXPORT_SYMBOL(phy_write_mmd);
/**
* __phy_modify() - Convenience function for modifying a PHY register
* @phydev: a pointer to a &struct phy_device
* @regnum: register number
* @mask: bit mask of bits to clear
* @set: bit mask of bits to set
*
* Unlocked helper function which allows a PHY register to be modified as
* new register value = (old register value & mask) | set
*/
int __phy_modify(struct phy_device *phydev, u32 regnum, u16 mask, u16 set)
{
int ret, res;
ret = __phy_read(phydev, regnum);
if (ret >= 0) {
res = __phy_write(phydev, regnum, (ret & ~mask) | set);
if (res < 0)
ret = res;
}
return ret;
}
EXPORT_SYMBOL_GPL(__phy_modify);
/**
* phy_modify - Convenience function for modifying a given PHY register
* @phydev: the phy_device struct
* @regnum: register number to write
* @mask: bit mask of bits to clear
* @set: new value of bits set in mask to write to @regnum
*
* NOTE: MUST NOT be called from interrupt context,
* because the bus read/write functions may wait for an interrupt
* to conclude the operation.
*/
int phy_modify(struct phy_device *phydev, u32 regnum, u16 mask, u16 set)
{
int ret;
mutex_lock(&phydev->mdio.bus->mdio_lock);
ret = __phy_modify(phydev, regnum, mask, set);
mutex_unlock(&phydev->mdio.bus->mdio_lock);
return ret;
}
EXPORT_SYMBOL_GPL(phy_modify);
static int __phy_read_page(struct phy_device *phydev)
{
return phydev->drv->read_page(phydev);
}
static int __phy_write_page(struct phy_device *phydev, int page)
{
return phydev->drv->write_page(phydev, page);
}
/**
* phy_save_page() - take the bus lock and save the current page
* @phydev: a pointer to a &struct phy_device
*
* Take the MDIO bus lock, and return the current page number. On error,
* returns a negative errno. phy_restore_page() must always be called
* after this, irrespective of success or failure of this call.
*/
int phy_save_page(struct phy_device *phydev)
{
mutex_lock(&phydev->mdio.bus->mdio_lock);
return __phy_read_page(phydev);
}
EXPORT_SYMBOL_GPL(phy_save_page);
/**
* phy_select_page() - take the bus lock, save the current page, and set a page
* @phydev: a pointer to a &struct phy_device
* @page: desired page
*
* Take the MDIO bus lock to protect against concurrent access, save the
* current PHY page, and set the current page. On error, returns a
* negative errno, otherwise returns the previous page number.
* phy_restore_page() must always be called after this, irrespective
* of success or failure of this call.
*/
int phy_select_page(struct phy_device *phydev, int page)
{
int ret, oldpage;
oldpage = ret = phy_save_page(phydev);
if (ret < 0)
return ret;
if (oldpage != page) {
ret = __phy_write_page(phydev, page);
if (ret < 0)
return ret;
}
return oldpage;
}
EXPORT_SYMBOL_GPL(phy_select_page);
/**
* phy_restore_page() - restore the page register and release the bus lock
* @phydev: a pointer to a &struct phy_device
* @oldpage: the old page, return value from phy_save_page() or phy_select_page()
* @ret: operation's return code
*
* Release the MDIO bus lock, restoring @oldpage if it is a valid page.
* This function propagates the earliest error code from the group of
* operations.
*
* Returns:
* @oldpage if it was a negative value, otherwise
* @ret if it was a negative errno value, otherwise
* phy_write_page()'s negative value if it were in error, otherwise
* @ret.
*/
int phy_restore_page(struct phy_device *phydev, int oldpage, int ret)
{
int r;
if (oldpage >= 0) {
r = __phy_write_page(phydev, oldpage);
/* Propagate the operation return code if the page write
* was successful.
*/
if (ret >= 0 && r < 0)
ret = r;
} else {
/* Propagate the phy page selection error code */
ret = oldpage;
}
mutex_unlock(&phydev->mdio.bus->mdio_lock);
return ret;
}
EXPORT_SYMBOL_GPL(phy_restore_page);
/**
* phy_read_paged() - Convenience function for reading a paged register
* @phydev: a pointer to a &struct phy_device
* @page: the page for the phy
* @regnum: register number
*
* Same rules as for phy_read().
*/
int phy_read_paged(struct phy_device *phydev, int page, u32 regnum)
{
int ret = 0, oldpage;
oldpage = phy_select_page(phydev, page);
if (oldpage >= 0)
ret = __phy_read(phydev, regnum);
return phy_restore_page(phydev, oldpage, ret);
}
EXPORT_SYMBOL(phy_read_paged);
/**
* phy_write_paged() - Convenience function for writing a paged register
* @phydev: a pointer to a &struct phy_device
* @page: the page for the phy
* @regnum: register number
* @val: value to write
*
* Same rules as for phy_write().
*/
int phy_write_paged(struct phy_device *phydev, int page, u32 regnum, u16 val)
{
int ret = 0, oldpage;
oldpage = phy_select_page(phydev, page);
if (oldpage >= 0)
ret = __phy_write(phydev, regnum, val);
return phy_restore_page(phydev, oldpage, ret);
}
EXPORT_SYMBOL(phy_write_paged);
/**
* phy_modify_paged() - Convenience function for modifying a paged register
* @phydev: a pointer to a &struct phy_device
* @page: the page for the phy
* @regnum: register number
* @mask: bit mask of bits to clear
* @set: bit mask of bits to set
*
* Same rules as for phy_read() and phy_write().
*/
int phy_modify_paged(struct phy_device *phydev, int page, u32 regnum,
u16 mask, u16 set)
{
int ret = 0, oldpage;
oldpage = phy_select_page(phydev, page);
if (oldpage >= 0)
ret = __phy_modify(phydev, regnum, mask, set);
return phy_restore_page(phydev, oldpage, ret);
}
EXPORT_SYMBOL(phy_modify_paged);
......@@ -1368,9 +1368,8 @@ static int genphy_config_eee_advert(struct phy_device *phydev)
*/
int genphy_setup_forced(struct phy_device *phydev)
{
int ctl = phy_read(phydev, MII_BMCR);
u16 ctl = 0;
ctl &= BMCR_LOOPBACK | BMCR_ISOLATE | BMCR_PDOWN;
phydev->pause = 0;
phydev->asym_pause = 0;
......@@ -1382,7 +1381,8 @@ int genphy_setup_forced(struct phy_device *phydev)
if (DUPLEX_FULL == phydev->duplex)
ctl |= BMCR_FULLDPLX;
return phy_write(phydev, MII_BMCR, ctl);
return phy_modify(phydev, MII_BMCR,
BMCR_LOOPBACK | BMCR_ISOLATE | BMCR_PDOWN, ctl);
}
EXPORT_SYMBOL(genphy_setup_forced);
......@@ -1392,17 +1392,9 @@ EXPORT_SYMBOL(genphy_setup_forced);
*/
int genphy_restart_aneg(struct phy_device *phydev)
{
int ctl = phy_read(phydev, MII_BMCR);
if (ctl < 0)
return ctl;
ctl |= BMCR_ANENABLE | BMCR_ANRESTART;
/* Don't isolate the PHY if we're negotiating */
ctl &= ~BMCR_ISOLATE;
return phy_write(phydev, MII_BMCR, ctl);
return phy_modify(phydev, MII_BMCR, ~BMCR_ISOLATE,
BMCR_ANENABLE | BMCR_ANRESTART);
}
EXPORT_SYMBOL(genphy_restart_aneg);
......@@ -1668,44 +1660,20 @@ EXPORT_SYMBOL(genphy_config_init);
int genphy_suspend(struct phy_device *phydev)
{
int value;
mutex_lock(&phydev->lock);
value = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, value | BMCR_PDOWN);
mutex_unlock(&phydev->lock);
return 0;
return phy_modify(phydev, MII_BMCR, 0, BMCR_PDOWN);
}
EXPORT_SYMBOL(genphy_suspend);
int genphy_resume(struct phy_device *phydev)
{
int value;
value = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN);
return 0;
return phy_modify(phydev, MII_BMCR, ~BMCR_PDOWN, 0);
}
EXPORT_SYMBOL(genphy_resume);
int genphy_loopback(struct phy_device *phydev, bool enable)
{
int value;
value = phy_read(phydev, MII_BMCR);
if (value < 0)
return value;
if (enable)
value |= BMCR_LOOPBACK;
else
value &= ~BMCR_LOOPBACK;
return phy_write(phydev, MII_BMCR, value);
return phy_modify(phydev, MII_BMCR, ~BMCR_LOOPBACK,
enable ? BMCR_LOOPBACK : 0);
}
EXPORT_SYMBOL(genphy_loopback);
......
......@@ -262,6 +262,9 @@ static inline u16 ethtool_adv_to_mmd_eee_adv_t(u32 adv)
return reg;
}
int __mdiobus_read(struct mii_bus *bus, int addr, u32 regnum);
int __mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val);
int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum);
int mdiobus_read_nested(struct mii_bus *bus, int addr, u32 regnum);
int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val);
......
......@@ -634,6 +634,9 @@ struct phy_driver {
int (*write_mmd)(struct phy_device *dev, int devnum, u16 regnum,
u16 val);
int (*read_page)(struct phy_device *dev);
int (*write_page)(struct phy_device *dev, int page);
/* Get the size and type of the eeprom contained within a plug-in
* module */
int (*module_info)(struct phy_device *dev,
......@@ -717,6 +720,18 @@ static inline int phy_read(struct phy_device *phydev, u32 regnum)
return mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, regnum);
}
/**
* __phy_read - convenience function for reading a given PHY register
* @phydev: the phy_device struct
* @regnum: register number to read
*
* The caller must have taken the MDIO bus lock.
*/
static inline int __phy_read(struct phy_device *phydev, u32 regnum)
{
return __mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, regnum);
}
/**
* phy_write - Convenience function for writing a given PHY register
* @phydev: the phy_device struct
......@@ -732,6 +747,23 @@ static inline int phy_write(struct phy_device *phydev, u32 regnum, u16 val)
return mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, regnum, val);
}
/**
* __phy_write - Convenience function for writing a given PHY register
* @phydev: the phy_device struct
* @regnum: register number to write
* @val: value to write to @regnum
*
* The caller must have taken the MDIO bus lock.
*/
static inline int __phy_write(struct phy_device *phydev, u32 regnum, u16 val)
{
return __mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, regnum,
val);
}
int __phy_modify(struct phy_device *phydev, u32 regnum, u16 mask, u16 set);
int phy_modify(struct phy_device *phydev, u32 regnum, u16 mask, u16 set);
/**
* phy_interrupt_is_valid - Convenience function for testing a given PHY irq
* @phydev: the phy_device struct
......@@ -810,6 +842,14 @@ static inline bool phy_is_pseudo_fixed_link(struct phy_device *phydev)
*/
int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val);
int phy_save_page(struct phy_device *phydev);
int phy_select_page(struct phy_device *phydev, int page);
int phy_restore_page(struct phy_device *phydev, int oldpage, int ret);
int phy_read_paged(struct phy_device *phydev, int page, u32 regnum);
int phy_write_paged(struct phy_device *phydev, int page, u32 regnum, u16 val);
int phy_modify_paged(struct phy_device *phydev, int page, u32 regnum,
u16 mask, u16 set);
struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id,
bool is_c45,
struct phy_c45_device_ids *c45_ids);
......
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