]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
net: eth-uclass: guard against reentrant eth_init()/eth_halt() calls
authorMatthias Schiffer <matthias.schiffer@ew.tq-group.com>
Fri, 26 Apr 2024 08:02:24 +0000 (10:02 +0200)
committerTom Rini <trini@konsulko.com>
Wed, 15 May 2024 16:46:47 +0000 (10:46 -0600)
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/
net/eth-uclass.c

index 4e3933fd05f35b4fbccbb26a6ec83b4502038f99..e34d7af0229e2eafba9b6c84b069a1359cb74c88 100644 (file)
@@ -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)