Skip to content

Commit

Permalink
Merge branch 'phy-mdio-refcnt'
Browse files Browse the repository at this point in the history
Russell King says:

====================
Phy, mdiobus, and netdev struct device fixes

The third version of this series fixes the build error which David
identified, and drops the broken changes for the Cavium Thunger BGX
ethernet driver as this driver requires some complex changes to
resolve the leakage - and this is best done by people who can test
the driver.

Compared to v2, the only patch which has changed is patch 6
  "net: fix phy refcounting in a bunch of drivers"

I _think_ I've been able to build-test all the drivers touched by
that patch to some degree now, though several of them needed the
Kconfig hacked to allow it (not all had || COMPILE_TEST clause on
their dependencies.)

Previous cover letters below:

This is the second version of the series, with the comments David had
on the first patch fixed up.  Original series description with updated
diffstat below.

While looking at the DSA code, I noticed we have a
of_find_net_device_by_node(), and it looks like users of that are
similarly buggy - it looks like net/dsa/dsa.c is the only user.  Fix
that too.

Hi,

While looking at the phy code, I identified a number of weaknesses
where refcounting on device structures was being leaked, where
modules could be removed while in-use, and where the fixed-phy could
end up having unintended consequences caused by incorrect calls to
fixed_phy_update_state().

This patch series resolves those issues, some of which were discovered
with testing on an Armada 388 board.  Not all patches are fully tested,
particularly the one which touches several network drivers.

When resolving the struct device refcounting problems, several different
solutions were considered before settling on the implementation here -
one of the considerations was to avoid touching many network drivers.
The solution here is:

	phy_attach*() - takes a refcount
	phy_detach*() - drops the phy_attach refcount

Provided drivers always attach and detach their phys, which they should
already be doing, this should change nothing, even if they leak a refcount.

	of_phy_find_device() and of_* functions which use that take
	a refcount.  Arrange for this refcount to be dropped once
	the phy is attached.

This is the reason why the previous change is important - we can't drop
this refcount taken by of_phy_find_device() until something else holds
a reference on the device.  This resolves the leaked refcount caused by
using of_phy_connect() or of_phy_attach().

Even without the above changes, these drivers are leaking by calling
of_phy_find_device().  These drivers are addressed by adding the
appropriate release of that refcount.

The mdiobus code also suffered from the same kind of leak, but thankfully
this only happened in one place - the mdio-mux code.

I also found that the try_module_get() in the phy layer code was utterly
useless: phydev->dev.driver was guaranteed to always be NULL, so
try_module_get() was always being called with a NULL argument.  I proved
this with my SFP code, which declares its own MDIO bus - the module use
count was never incremented irrespective of how I set the MDIO bus up.
This allowed the MDIO bus code to be removed from the kernel while there
were still PHYs attached to it.

One other bug was discovered: while using in-band-status with mvneta, it
was found that if a real phy is attached with in-band-status enabled,
and another ethernet interface is using the fixed-phy infrastructure, the
interface using the fixed-phy infrastructure is configured according to
the other interface using the in-band-status - which is caused by the
fixed-phy code not verifying that the phy_device passed in is actually
a fixed-phy device, rather than a real MDIO phy.

Lastly, having mdio_bus reversing phy_device_register() internals seems
like a layering violation - it's trivial to move that code to the phy
device layer.
====================

Tested-by: Florian Fainelli <f.fainelli@gmail.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Sep 25, 2015
2 parents 17a10c9 + 9861f72 commit b626ef0
Show file tree
Hide file tree
Showing 13 changed files with 181 additions and 51 deletions.
24 changes: 16 additions & 8 deletions drivers/net/ethernet/apm/xgene/xgene_enet_hw.c
Original file line number Diff line number Diff line change
Expand Up @@ -689,16 +689,24 @@ static int xgene_enet_phy_connect(struct net_device *ndev)
netdev_dbg(ndev, "No phy-handle found in DT\n");
return -ENODEV;
}
pdata->phy_dev = of_phy_find_device(phy_np);
}

phy_dev = pdata->phy_dev;
phy_dev = of_phy_connect(ndev, phy_np, &xgene_enet_adjust_link,
0, pdata->phy_mode);
if (!phy_dev) {
netdev_err(ndev, "Could not connect to PHY\n");
return -ENODEV;
}

pdata->phy_dev = phy_dev;
} else {
phy_dev = pdata->phy_dev;

if (!phy_dev ||
phy_connect_direct(ndev, phy_dev, &xgene_enet_adjust_link,
pdata->phy_mode)) {
netdev_err(ndev, "Could not connect to PHY\n");
return -ENODEV;
if (!phy_dev ||
phy_connect_direct(ndev, phy_dev, &xgene_enet_adjust_link,
pdata->phy_mode)) {
netdev_err(ndev, "Could not connect to PHY\n");
return -ENODEV;
}
}

pdata->phy_speed = SPEED_UNKNOWN;
Expand Down
6 changes: 5 additions & 1 deletion drivers/net/ethernet/freescale/gianfar.c
Original file line number Diff line number Diff line change
Expand Up @@ -1710,8 +1710,10 @@ static void gfar_configure_serdes(struct net_device *dev)
* everything for us? Resetting it takes the link down and requires
* several seconds for it to come back.
*/
if (phy_read(tbiphy, MII_BMSR) & BMSR_LSTATUS)
if (phy_read(tbiphy, MII_BMSR) & BMSR_LSTATUS) {
put_device(&tbiphy->dev);
return;
}

/* Single clk mode, mii mode off(for serdes communication) */
phy_write(tbiphy, MII_TBICON, TBICON_CLK_SELECT);
Expand All @@ -1723,6 +1725,8 @@ static void gfar_configure_serdes(struct net_device *dev)
phy_write(tbiphy, MII_BMCR,
BMCR_ANENABLE | BMCR_ANRESTART | BMCR_FULLDPLX |
BMCR_SPEED1000);

put_device(&tbiphy->dev);
}

static int __gfar_is_rx_idle(struct gfar_private *priv)
Expand Down
8 changes: 7 additions & 1 deletion drivers/net/ethernet/freescale/ucc_geth.c
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,8 @@ static int adjust_enet_interface(struct ucc_geth_private *ugeth)
value = phy_read(tbiphy, ENET_TBI_MII_CR);
value &= ~0x1000; /* Turn off autonegotiation */
phy_write(tbiphy, ENET_TBI_MII_CR, value);

put_device(&tbiphy->dev);
}

init_check_frame_length_mode(ug_info->lengthCheckRx, &ug_regs->maccfg2);
Expand Down Expand Up @@ -1702,15 +1704,19 @@ static void uec_configure_serdes(struct net_device *dev)
* everything for us? Resetting it takes the link down and requires
* several seconds for it to come back.
*/
if (phy_read(tbiphy, ENET_TBI_MII_SR) & TBISR_LSTATUS)
if (phy_read(tbiphy, ENET_TBI_MII_SR) & TBISR_LSTATUS) {
put_device(&tbiphy->dev);
return;
}

/* Single clk mode, mii mode off(for serdes communication) */
phy_write(tbiphy, ENET_TBI_MII_ANA, TBIANA_SETTINGS);

phy_write(tbiphy, ENET_TBI_MII_TBICON, TBICON_CLK_SELECT);

phy_write(tbiphy, ENET_TBI_MII_CR, TBICR_SETTINGS);

put_device(&tbiphy->dev);
}

/* Configure the PHY for dev.
Expand Down
2 changes: 2 additions & 0 deletions drivers/net/ethernet/marvell/mvneta.c
Original file line number Diff line number Diff line change
Expand Up @@ -3175,6 +3175,8 @@ static int mvneta_probe(struct platform_device *pdev)
struct phy_device *phy = of_phy_find_device(dn);

mvneta_fixed_link_update(pp, phy);

put_device(&phy->dev);
}

return 0;
Expand Down
2 changes: 2 additions & 0 deletions drivers/net/ethernet/xilinx/xilinx_emaclite.c
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,8 @@ static int xemaclite_mdio_setup(struct net_local *lp, struct device *dev)
if (!phydev)
dev_info(dev,
"MDIO of the phy is not registered yet\n");
else
put_device(&phydev->dev);
return 0;
}

Expand Down
2 changes: 1 addition & 1 deletion drivers/net/phy/fixed_phy.c
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ int fixed_phy_update_state(struct phy_device *phydev,
struct fixed_mdio_bus *fmb = &platform_fmb;
struct fixed_phy *fp;

if (!phydev || !phydev->bus)
if (!phydev || phydev->bus != fmb->mii_bus)
return -EINVAL;

list_for_each_entry(fp, &fmb->phys, node) {
Expand Down
19 changes: 13 additions & 6 deletions drivers/net/phy/mdio-mux.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,18 @@ int mdio_mux_init(struct device *dev,
if (!parent_bus_node)
return -ENODEV;

parent_bus = of_mdio_find_bus(parent_bus_node);
if (parent_bus == NULL) {
ret_val = -EPROBE_DEFER;
goto err_parent_bus;
}

pb = devm_kzalloc(dev, sizeof(*pb), GFP_KERNEL);
if (pb == NULL) {
ret_val = -ENOMEM;
goto err_parent_bus;
}

parent_bus = of_mdio_find_bus(parent_bus_node);
if (parent_bus == NULL) {
ret_val = -EPROBE_DEFER;
goto err_parent_bus;
}

pb->switch_data = data;
pb->switch_fn = switch_fn;
pb->current_child = -1;
Expand Down Expand Up @@ -173,6 +173,10 @@ int mdio_mux_init(struct device *dev,
dev_info(dev, "Version " DRV_VERSION "\n");
return 0;
}

/* balance the reference of_mdio_find_bus() took */
put_device(&pb->mii_bus->dev);

err_parent_bus:
of_node_put(parent_bus_node);
return ret_val;
Expand All @@ -189,6 +193,9 @@ void mdio_mux_uninit(void *mux_handle)
mdiobus_free(cb->mii_bus);
cb = cb->next;
}

/* balance the reference of_mdio_find_bus() in mdio_mux_init() took */
put_device(&pb->mii_bus->dev);
}
EXPORT_SYMBOL_GPL(mdio_mux_uninit);

Expand Down
24 changes: 16 additions & 8 deletions drivers/net/phy/mdio_bus.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ static int of_mdio_bus_match(struct device *dev, const void *mdio_bus_np)
* of_mdio_find_bus - Given an mii_bus node, find the mii_bus.
* @mdio_bus_np: Pointer to the mii_bus.
*
* Returns a pointer to the mii_bus, or NULL if none found.
* Returns a reference to the mii_bus, or NULL if none found. The
* embedded struct device will have its reference count incremented,
* and this must be put once the bus is finished with.
*
* Because the association of a device_node and mii_bus is made via
* of_mdiobus_register(), the mii_bus cannot be found before it is
Expand Down Expand Up @@ -242,7 +244,7 @@ static inline void of_mdiobus_link_phydev(struct mii_bus *mdio,
*
* Returns 0 on success or < 0 on error.
*/
int mdiobus_register(struct mii_bus *bus)
int __mdiobus_register(struct mii_bus *bus, struct module *owner)
{
int i, err;

Expand All @@ -253,6 +255,7 @@ int mdiobus_register(struct mii_bus *bus)
BUG_ON(bus->state != MDIOBUS_ALLOCATED &&
bus->state != MDIOBUS_UNREGISTERED);

bus->owner = owner;
bus->dev.parent = bus->parent;
bus->dev.class = &mdio_bus_class;
bus->dev.groups = NULL;
Expand Down Expand Up @@ -288,13 +291,16 @@ int mdiobus_register(struct mii_bus *bus)

error:
while (--i >= 0) {
if (bus->phy_map[i])
device_unregister(&bus->phy_map[i]->dev);
struct phy_device *phydev = bus->phy_map[i];
if (phydev) {
phy_device_remove(phydev);
phy_device_free(phydev);
}
}
device_del(&bus->dev);
return err;
}
EXPORT_SYMBOL(mdiobus_register);
EXPORT_SYMBOL(__mdiobus_register);

void mdiobus_unregister(struct mii_bus *bus)
{
Expand All @@ -304,9 +310,11 @@ void mdiobus_unregister(struct mii_bus *bus)
bus->state = MDIOBUS_UNREGISTERED;

for (i = 0; i < PHY_MAX_ADDR; i++) {
if (bus->phy_map[i])
device_unregister(&bus->phy_map[i]->dev);
bus->phy_map[i] = NULL;
struct phy_device *phydev = bus->phy_map[i];
if (phydev) {
phy_device_remove(phydev);
phy_device_free(phydev);
}
}
device_del(&bus->dev);
}
Expand Down
62 changes: 48 additions & 14 deletions drivers/net/phy/phy_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,24 @@ int phy_device_register(struct phy_device *phydev)
}
EXPORT_SYMBOL(phy_device_register);

/**
* phy_device_remove - Remove a previously registered phy device from the MDIO bus
* @phydev: phy_device structure to remove
*
* This doesn't free the phy_device itself, it merely reverses the effects
* of phy_device_register(). Use phy_device_free() to free the device
* after calling this function.
*/
void phy_device_remove(struct phy_device *phydev)
{
struct mii_bus *bus = phydev->bus;
int addr = phydev->addr;

device_del(&phydev->dev);
bus->phy_map[addr] = NULL;
}
EXPORT_SYMBOL(phy_device_remove);

/**
* phy_find_first - finds the first PHY device on the bus
* @bus: the target MII bus
Expand Down Expand Up @@ -578,14 +596,22 @@ EXPORT_SYMBOL(phy_init_hw);
* generic driver is used. The phy_device is given a ptr to
* the attaching device, and given a callback for link status
* change. The phy_device is returned to the attaching driver.
* This function takes a reference on the phy device.
*/
int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
u32 flags, phy_interface_t interface)
{
struct mii_bus *bus = phydev->bus;
struct device *d = &phydev->dev;
struct module *bus_module;
int err;

if (!try_module_get(bus->owner)) {
dev_err(&dev->dev, "failed to get the bus module\n");
return -EIO;
}

get_device(d);

/* Assume that if there is no driver, that it doesn't
* exist, and we should use the genphy driver.
*/
Expand All @@ -600,20 +626,13 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
err = device_bind_driver(d);

if (err)
return err;
goto error;
}

if (phydev->attached_dev) {
dev_err(&dev->dev, "PHY already attached\n");
return -EBUSY;
}

/* Increment the bus module reference count */
bus_module = phydev->bus->dev.driver ?
phydev->bus->dev.driver->owner : NULL;
if (!try_module_get(bus_module)) {
dev_err(&dev->dev, "failed to get the bus module\n");
return -EIO;
err = -EBUSY;
goto error;
}

phydev->attached_dev = dev;
Expand All @@ -636,6 +655,11 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
phy_resume(phydev);

return err;

error:
put_device(d);
module_put(bus->owner);
return err;
}
EXPORT_SYMBOL(phy_attach_direct);

Expand Down Expand Up @@ -677,14 +701,15 @@ EXPORT_SYMBOL(phy_attach);
/**
* phy_detach - detach a PHY device from its network device
* @phydev: target phy_device struct
*
* This detaches the phy device from its network device and the phy
* driver, and drops the reference count taken in phy_attach_direct().
*/
void phy_detach(struct phy_device *phydev)
{
struct mii_bus *bus;
int i;

if (phydev->bus->dev.driver)
module_put(phydev->bus->dev.driver->owner);

phydev->attached_dev->phydev = NULL;
phydev->attached_dev = NULL;
phy_suspend(phydev);
Expand All @@ -700,6 +725,15 @@ void phy_detach(struct phy_device *phydev)
break;
}
}

/*
* The phydev might go away on the put_device() below, so avoid
* a use-after-free bug by reading the underlying bus first.
*/
bus = phydev->bus;

put_device(&phydev->dev);
module_put(bus->owner);
}
EXPORT_SYMBOL(phy_detach);

Expand Down
Loading

0 comments on commit b626ef0

Please sign in to comment.