net: ethernet: ti: cpsw: fix race condition during open()
diff mbox

Message ID 509439ff3e756ea1dade2738e305f08fc7920650.1491220439.git.nsekhar@ti.com
State New
Headers show

Commit Message

Sekhar Nori April 3, 2017, 12:04 p.m. UTC
TI's cpsw driver handles both OF and non-OF case for phy
connect. Unfortunately of_phy_connect() returns NULL on
error while phy_connect() returns ERR_PTR().

To handle this, cpsw_slave_open() overrides the return value
from phy_connect() to make it NULL or error.

This leaves a small window, where cpsw_adjust_link() may be
invoked for a slave while slave->phy pointer is temporarily
set to -ENODEV (or some other error) before it is finally set
to NULL.

_cpsw_adjust_link() only handles the NULL case, and an oops
results when ERR_PTR() is seen by it.

Note that cpsw_adjust_link() checks PHY status for each
slave whenever it is invoked. It can so happen that even
though phy_connect() for a given slave returns error,
_cpsw_adjust_link() is still called for that slave because
the link status of another slave changed.

Fix this by using a temporary pointer to store return value
of {of_}phy_connect() and do a one-time write to slave->phy.

Reviewed-by: Grygorii Strashko <grygorii.strashko@ti.com>
Reported-by: Yan Liu <yan-liu@ti.com>
Signed-off-by: Sekhar Nori <nsekhar@ti.com>
---
 drivers/net/ethernet/ti/cpsw.c | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

Comments

David Miller April 4, 2017, 5:33 p.m. UTC | #1
From: Sekhar Nori <nsekhar@ti.com>
Date: Mon, 3 Apr 2017 17:34:28 +0530

> TI's cpsw driver handles both OF and non-OF case for phy
> connect. Unfortunately of_phy_connect() returns NULL on
> error while phy_connect() returns ERR_PTR().
> 
> To handle this, cpsw_slave_open() overrides the return value
> from phy_connect() to make it NULL or error.
> 
> This leaves a small window, where cpsw_adjust_link() may be
> invoked for a slave while slave->phy pointer is temporarily
> set to -ENODEV (or some other error) before it is finally set
> to NULL.
> 
> _cpsw_adjust_link() only handles the NULL case, and an oops
> results when ERR_PTR() is seen by it.
> 
> Note that cpsw_adjust_link() checks PHY status for each
> slave whenever it is invoked. It can so happen that even
> though phy_connect() for a given slave returns error,
> _cpsw_adjust_link() is still called for that slave because
> the link status of another slave changed.
> 
> Fix this by using a temporary pointer to store return value
> of {of_}phy_connect() and do a one-time write to slave->phy.
> 
> Reviewed-by: Grygorii Strashko <grygorii.strashko@ti.com>
> Reported-by: Yan Liu <yan-liu@ti.com>
> Signed-off-by: Sekhar Nori <nsekhar@ti.com>

Applied, thank you.
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch
diff mbox

diff --git a/drivers/net/ethernet/ti/cpsw.c b/drivers/net/ethernet/ti/cpsw.c
index 9f3d9c67e3fe..5a4faa4489d0 100644
--- a/drivers/net/ethernet/ti/cpsw.c
+++ b/drivers/net/ethernet/ti/cpsw.c
@@ -1267,6 +1267,7 @@  static void soft_reset_slave(struct cpsw_slave *slave)
 static void cpsw_slave_open(struct cpsw_slave *slave, struct cpsw_priv *priv)
 {
 	u32 slave_port;
+	struct phy_device *phy;
 	struct cpsw_common *cpsw = priv->cpsw;
 
 	soft_reset_slave(slave);
@@ -1300,27 +1301,28 @@  static void cpsw_slave_open(struct cpsw_slave *slave, struct cpsw_priv *priv)
 				   1 << slave_port, 0, 0, ALE_MCAST_FWD_2);
 
 	if (slave->data->phy_node) {
-		slave->phy = of_phy_connect(priv->ndev, slave->data->phy_node,
+		phy = of_phy_connect(priv->ndev, slave->data->phy_node,
 				 &cpsw_adjust_link, 0, slave->data->phy_if);
-		if (!slave->phy) {
+		if (!phy) {
 			dev_err(priv->dev, "phy \"%s\" not found on slave %d\n",
 				slave->data->phy_node->full_name,
 				slave->slave_num);
 			return;
 		}
 	} else {
-		slave->phy = phy_connect(priv->ndev, slave->data->phy_id,
+		phy = phy_connect(priv->ndev, slave->data->phy_id,
 				 &cpsw_adjust_link, slave->data->phy_if);
-		if (IS_ERR(slave->phy)) {
+		if (IS_ERR(phy)) {
 			dev_err(priv->dev,
 				"phy \"%s\" not found on slave %d, err %ld\n",
 				slave->data->phy_id, slave->slave_num,
-				PTR_ERR(slave->phy));
-			slave->phy = NULL;
+				PTR_ERR(phy));
 			return;
 		}
 	}
 
+	slave->phy = phy;
+
 	phy_attached_info(slave->phy);
 
 	phy_start(slave->phy);