From: Matthias Schiffer <matthias.schiffer@ew.tq-group.com>
Date: Fri, 26 Apr 2024 08:02:24 +0000 (+0200)
Subject: net: eth-uclass: guard against reentrant eth_init()/eth_halt() calls
X-Git-Tag: v2025.01-rc5-pxa1908~409^2~17
X-Git-Url: http://git.dujemihanovic.xyz/img/static/html/%7B%7B?a=commitdiff_plain;h=ef9d4da6c92b07f0668f059d0cdc51eb34ed947e;p=u-boot.git

net: eth-uclass: guard against reentrant eth_init()/eth_halt() calls

With netconsole, any log message can result in an eth_init(), possibly
causing an reentrant call into eth_init() if a driver's ops print
anything:

    eth_init() -> driver.start() -> printf() -> netconsole -> eth_init()
    eth_halt() -> driver.stop() -> printf() -> netconsole -> eth_init()

Rather than expecting every single Ethernet driver to handle this case,
prevent the reentrant calls in eth_init() and eth_halt().

The issue was noticed on an AM62x board, where a bootm after
simultaneous netconsole and TFTP would result in a crash.

Signed-off-by: Matthias Schiffer <matthias.schiffer@ew.tq-group.com>
Link: https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1350550/sk-am62a-lp-am65_cpsw_nuss_port-error-in-u-boot-while-using-netconsole-functionality/
---

diff --git a/net/eth-uclass.c b/net/eth-uclass.c
index 4e3933fd05..e34d7af022 100644
--- a/net/eth-uclass.c
+++ b/net/eth-uclass.c
@@ -47,6 +47,8 @@ struct eth_uclass_priv {
 
 /* eth_errno - This stores the most recent failure code from DM functions */
 static int eth_errno;
+/* Are we currently in eth_init() or eth_halt()? */
+static bool in_init_halt;
 
 /* board-specific Ethernet Interface initializations. */
 __weak int board_interface_eth_init(struct udevice *dev,
@@ -284,11 +286,19 @@ U_BOOT_ENV_CALLBACK(ethaddr, on_ethaddr);
 
 int eth_init(void)
 {
-	char *ethact = env_get("ethact");
-	char *ethrotate = env_get("ethrotate");
 	struct udevice *current = NULL;
 	struct udevice *old_current;
 	int ret = -ENODEV;
+	char *ethrotate;
+	char *ethact;
+
+	if (in_init_halt)
+		return -EBUSY;
+
+	in_init_halt = true;
+
+	ethact = env_get("ethact");
+	ethrotate = env_get("ethrotate");
 
 	/*
 	 * When 'ethrotate' variable is set to 'no' and 'ethact' variable
@@ -297,8 +307,10 @@ int eth_init(void)
 	if ((ethrotate != NULL) && (strcmp(ethrotate, "no") == 0)) {
 		if (ethact) {
 			current = eth_get_dev_by_name(ethact);
-			if (!current)
-				return -EINVAL;
+			if (!current) {
+				ret = -EINVAL;
+				goto end;
+			}
 		}
 	}
 
@@ -306,7 +318,8 @@ int eth_init(void)
 		current = eth_get_dev();
 		if (!current) {
 			log_err("No ethernet found.\n");
-			return -ENODEV;
+			ret = -ENODEV;
+			goto end;
 		}
 	}
 
@@ -323,7 +336,8 @@ int eth_init(void)
 
 					priv->state = ETH_STATE_ACTIVE;
 					priv->running = true;
-					return 0;
+					ret = 0;
+					goto end;
 				}
 			} else {
 				ret = eth_errno;
@@ -343,6 +357,8 @@ int eth_init(void)
 		current = eth_get_dev();
 	} while (old_current != current);
 
+end:
+	in_init_halt = false;
 	return ret;
 }
 
@@ -351,17 +367,25 @@ void eth_halt(void)
 	struct udevice *current;
 	struct eth_device_priv *priv;
 
+	if (in_init_halt)
+		return;
+
+	in_init_halt = true;
+
 	current = eth_get_dev();
 	if (!current)
-		return;
+		goto end;
 
 	priv = dev_get_uclass_priv(current);
 	if (!priv || !priv->running)
-		return;
+		goto end;
 
 	eth_get_ops(current)->stop(current);
 	priv->state = ETH_STATE_PASSIVE;
 	priv->running = false;
+
+end:
+	in_init_halt = false;
 }
 
 int eth_is_active(struct udevice *dev)