Reuse the Ethernet device already started by net_start_again() when
attaching the replacement lwIP runtime. Starting it twice can reset
freshly initialized state and lose packets.

Clear the previous attempt's wrap marker before selecting an interface,
retain the new marker for BOOTP_MAY_FAIL, and preserve the legacy
ten-second wait after rotation wraps. Keep polling and allow Ctrl-C
during that wait. Callers still detach their runtime on all errors.

Exercise start/stop counts, wrap handling, the delay and cancellation
with the sandbox Ethernet driver.

Signed-off-by: James Hilliard <[email protected]>
---
 arch/sandbox/include/asm/eth.h |   6 +++
 drivers/net/sandbox.c          |   6 +++
 net/lwip/net-lwip.c            |  72 +++++++++++++++++++-------
 test/dm/lwip.c                 | 111 +++++++++++++++++++++++++++++++++++++++++
 test/dm/lwip_dhcp_policy.c     |  19 +++++++
 5 files changed, 195 insertions(+), 19 deletions(-)

diff --git a/arch/sandbox/include/asm/eth.h b/arch/sandbox/include/asm/eth.h
index 083a7371a3f..43ea0d25ce0 100644
--- a/arch/sandbox/include/asm/eth.h
+++ b/arch/sandbox/include/asm/eth.h
@@ -82,6 +82,9 @@ typedef int sandbox_eth_tx_hand_f(struct udevice *dev, void 
*pkt,
  * recv_packet_buffer - buffers of the packet returned as received
  * recv_packet_length - lengths of the packet returned as received
  * recv_packets - number of packets returned
+ * start_count - number of calls to the driver's start operation
+ * stop_count - number of calls to the driver's stop operation
+ * restart_wrap - inject an Ethernet rotation wrap on start
  * tx_handler - function to generate responses to sent packets
  * priv - a pointer to some structure a test may want to keep track of
  */
@@ -94,6 +97,9 @@ struct eth_sandbox_priv {
        uchar * recv_packet_buffer[PKTBUFSRX];
        int recv_packet_length[PKTBUFSRX];
        int recv_packets;
+       unsigned int start_count;
+       unsigned int stop_count;
+       bool restart_wrap;
        sandbox_eth_tx_hand_f *tx_handler;
        void *priv;
 };
diff --git a/drivers/net/sandbox.c b/drivers/net/sandbox.c
index e1daeb6c1e6..d9946fbf1ad 100644
--- a/drivers/net/sandbox.c
+++ b/drivers/net/sandbox.c
@@ -400,6 +400,9 @@ static int sb_eth_start(struct udevice *dev)
 
        debug("eth_sandbox: Start\n");
 
+       priv->start_count++;
+       if (priv->restart_wrap)
+               net_restart_wrap = 1;
        priv->recv_packets = 0;
        for (int i = 0; i < PKTBUFSRX; i++) {
                priv->recv_packet_buffer[i] = net_rx_packets[i];
@@ -463,6 +466,9 @@ static int sb_eth_free_pkt(struct udevice *dev, uchar 
*packet, int length)
 
 static void sb_eth_stop(struct udevice *dev)
 {
+       struct eth_sandbox_priv *priv = dev_get_priv(dev);
+
+       priv->stop_count++;
        debug("eth_sandbox: Stop\n");
 }
 
diff --git a/net/lwip/net-lwip.c b/net/lwip/net-lwip.c
index ac8d3906fd9..df11322ec3b 100644
--- a/net/lwip/net-lwip.c
+++ b/net/lwip/net-lwip.c
@@ -3,6 +3,7 @@
 /* Copyright (C) 2024 Linaro Ltd. */
 
 #include <command.h>
+#include <console.h>
 #include <env.h>
 #include <image.h>
 #include <dm/device.h>
@@ -22,6 +23,7 @@
 #include <net.h>
 #include <net/pcap.h>
 #include <timer.h>
+#include <time.h>
 #include <u-boot/schedule.h>
 
 /* xx:xx:xx:xx:xx:xx\0 */
@@ -367,21 +369,9 @@ static int net_lwip_configure(enum net_lwip_addr_mode 
addr_mode)
        return 0;
 }
 
-/**
- * net_lwip_start - Attach a client to the shared lwIP runtime
- * @ctx: Zero-initialized client attachment
- * @addr_mode: Initial IPv4 address configuration requested by the client
- *
- * The first client starts the selected Ethernet device and creates the lwIP
- * network interface. Later clients share both resources. Strict environment
- * clients and address-less clients are mutually exclusive. Flexible clients
- * can remain attached while an address-less client temporarily owns the
- * interface configuration.
- *
- * Return: 0 on success, or a negative error code.
- */
-int net_lwip_start(struct net_lwip_ctx *ctx,
-                  enum net_lwip_addr_mode addr_mode)
+static int net_lwip_start_ctx(struct net_lwip_ctx *ctx,
+                             enum net_lwip_addr_mode addr_mode,
+                             bool eth_started)
 {
        struct netif *netif;
        int ret;
@@ -402,9 +392,12 @@ int net_lwip_start(struct net_lwip_ctx *ctx,
                return -EBUSY;
 
        if (!net_lwip_runtime.users) {
-               ret = net_lwip_eth_start();
-               if (ret)
-                       return ret;
+               /* net_start_again() already selected and started the next NIC. 
*/
+               if (!eth_started) {
+                       ret = net_lwip_eth_start();
+                       if (ret)
+                               return ret;
+               }
 
                net_lwip_runtime.dev = eth_get_dev();
                netif = new_netif(net_lwip_runtime.dev,
@@ -443,6 +436,25 @@ int net_lwip_start(struct net_lwip_ctx *ctx,
        return 0;
 }
 
+/**
+ * net_lwip_start - Attach a client to the shared lwIP runtime
+ * @ctx: Zero-initialized client attachment
+ * @addr_mode: Initial IPv4 address configuration requested by the client
+ *
+ * The first client starts the selected Ethernet device and creates the lwIP
+ * network interface. Later clients share both resources. Strict environment
+ * clients and address-less clients are mutually exclusive. Flexible clients
+ * can remain attached while an address-less client temporarily owns the
+ * interface configuration.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+int net_lwip_start(struct net_lwip_ctx *ctx,
+                  enum net_lwip_addr_mode addr_mode)
+{
+       return net_lwip_start_ctx(ctx, addr_mode, false);
+}
+
 /**
  * net_lwip_stop - Detach a client from the shared lwIP runtime
  * @ctx: Active client attachment
@@ -492,6 +504,8 @@ void net_lwip_stop(struct net_lwip_ctx *ctx)
  * Stop the current interface, select the next interface according to the
  * normal network retry policy and attach @ctx to the replacement interface.
  * A shared runtime cannot be restarted without disrupting other clients.
+ * Wait ten seconds after wrapping around the interfaces, as in legacy.
+ * Callers must detach @ctx even on error, including an interrupted wait.
  *
  * Return: 0 on success, -EBUSY if other clients are attached, or another
  * negative error code.
@@ -499,6 +513,7 @@ void net_lwip_stop(struct net_lwip_ctx *ctx)
 int net_lwip_restart(struct net_lwip_ctx *ctx)
 {
        enum net_lwip_addr_mode addr_mode;
+       ulong start;
        int ret;
 
        if (!ctx || ctx->netif != net_lwip_runtime.netif ||
@@ -510,11 +525,30 @@ int net_lwip_restart(struct net_lwip_ctx *ctx)
        addr_mode = ctx->addr_mode;
        net_lwip_stop(ctx);
 
+       /* Retain the result for callers such as BOOTP_MAY_FAIL, but do not
+        * carry the previous attempt's wrap into this interface selection.
+        */
+       net_restart_wrap = 0;
        ret = net_start_again();
        if (ret)
                return ret;
 
-       return net_lwip_start(ctx, addr_mode);
+       ret = net_lwip_start_ctx(ctx, addr_mode, true);
+       if (ret)
+               return ret;
+
+       if (net_restart_wrap) {
+               start = get_timer(0);
+               while (get_timer(start) < 10000) {
+                       if (ctrlc()) {
+                               puts("\nAbort\n");
+                               return -EINTR;
+                       }
+                       net_lwip_poll();
+               }
+       }
+
+       return 0;
 }
 
 /**
diff --git a/test/dm/lwip.c b/test/dm/lwip.c
index 5e3d26eaccf..8bb7e2bad9a 100644
--- a/test/dm/lwip.c
+++ b/test/dm/lwip.c
@@ -1,11 +1,14 @@
 // SPDX-License-Identifier: GPL-2.0+
 /* Copyright 2026 James Hilliard <[email protected]> */
 
+#include <console.h>
 #include <dm.h>
 #include <env.h>
 #include <lwip/pbuf.h>
 #include <lwip/udp.h>
+#include <lwip/timeouts.h>
 #include <net.h>
+#include <time.h>
 #include <asm/eth.h>
 #include <dm/test.h>
 #include <linux/kconfig.h>
@@ -375,3 +378,111 @@ static int dm_test_lwip_runtime(struct unit_test_state 
*uts)
 }
 
 DM_TEST(dm_test_lwip_runtime, UTF_SCAN_FDT);
+
+struct lwip_restart_test {
+       unsigned int ticks;
+       bool cancel;
+};
+
+static void lwip_restart_tick(void *arg)
+{
+       struct lwip_restart_test *test = arg;
+
+       test->ticks++;
+       if (test->cancel)
+               console_in_puts("\x03");
+       timer_test_add_offset(1000);
+       sys_timeout(1, lwip_restart_tick, test);
+}
+
+static int lwip_restart_check(struct unit_test_state *uts,
+                             struct net_lwip_ctx *net,
+                             struct lwip_restart_test *test)
+{
+       struct eth_sandbox_priv *priv;
+       unsigned int starts, stops;
+       ulong start;
+       int ret;
+
+       ut_assertok(env_set("ethact", "eth@10002000"));
+       ut_assertok(env_set("ethrotate", "no"));
+       ut_assertok(env_set("netretry", "yes"));
+       ut_assertok(net_lwip_start(net, NET_LWIP_ADDR_NONE));
+       priv = dev_get_priv(net->dev);
+       starts = priv->start_count;
+       stops = priv->stop_count;
+       net_try_count = 1;
+       net_restart_wrap = 0;
+       ut_assertok(net_lwip_restart(net));
+       ut_asserteq(starts + 1, priv->start_count);
+       ut_asserteq(stops + 1, priv->stop_count);
+       ut_assert(eth_is_active(net->dev));
+
+       /* Exercise the wrap signal without depending on sandbox's NIC count. */
+       priv->restart_wrap = true;
+       start = get_timer(0);
+       sys_timeout(1, lwip_restart_tick, test);
+       ret = net_lwip_restart(net);
+       priv->restart_wrap = false;
+       sys_untimeout(lwip_restart_tick, test);
+       ut_assertok(ret);
+       ut_assert(get_timer(start) >= 10000);
+       ut_assert(test->ticks >= 10);
+       ut_asserteq(1, net_restart_wrap);
+       ut_asserteq(starts + 2, priv->start_count);
+       ut_asserteq(stops + 2, priv->stop_count);
+
+       /* No inherited delay on the next retry; Ctrl-C cancels a new wait. */
+       start = get_timer(0);
+       ut_assertok(net_lwip_restart(net));
+       ut_assert(get_timer(start) < 1000);
+       ut_asserteq(0, net_restart_wrap);
+       test->cancel = true;
+       priv->restart_wrap = true;
+       start = get_timer(0);
+       sys_timeout(1, lwip_restart_tick, test);
+       ret = net_lwip_restart(net);
+       priv->restart_wrap = false;
+       sys_untimeout(lwip_restart_tick, test);
+       ut_asserteq(-EINTR, ret);
+       ut_assert(get_timer(start) < 10000);
+       ut_asserteq(1, net_restart_wrap);
+       ut_asserteq(starts + 4, priv->start_count);
+       ut_asserteq(stops + 4, priv->stop_count);
+       return 0;
+}
+
+static int dm_test_lwip_restart(struct unit_test_state *uts)
+{
+       const char * const vars[] = { "ethact", "ethrotate", "netretry" };
+       struct lwip_restart_test test = {};
+       struct net_lwip_ctx net = {};
+       char *saved[ARRAY_SIZE(vars)] = {};
+       int i, ret = -ENOMEM, ctrlc_state;
+
+       for (i = 0; i < ARRAY_SIZE(vars); i++) {
+               const char *value = env_get(vars[i]);
+
+               if (value) {
+                       saved[i] = strdup(value);
+                       if (!saved[i])
+                               goto out;
+               }
+       }
+       ctrlc_state = disable_ctrlc(0);
+       ret = lwip_restart_check(uts, &net, &test);
+       sys_untimeout(lwip_restart_tick, &test);
+       net_lwip_stop(&net);
+       clear_ctrlc();
+       disable_ctrlc(ctrlc_state);
+       net_restart_wrap = 0;
+       for (i = 0; i < ARRAY_SIZE(vars); i++)
+               if (env_set(vars[i], saved[i]))
+                       ret = -EINVAL;
+out:
+       for (i = 0; i < ARRAY_SIZE(vars); i++)
+               free(saved[i]);
+       return ret;
+}
+
+DM_TEST(dm_test_lwip_restart, UTF_SCAN_FDT | UTF_CONSOLE);
diff --git a/test/dm/lwip_dhcp_policy.c b/test/dm/lwip_dhcp_policy.c
index d60bebcd86b..1e55a0c097a 100644
--- a/test/dm/lwip_dhcp_policy.c
+++ b/test/dm/lwip_dhcp_policy.c
@@ -104,6 +104,8 @@ static int dhcp_policy_check(struct unit_test_state *uts, 
struct dhcp_policy_tes
        };
        static const char * const invalid_interval[] = { "0", "32767501", 
"4294967796" };
        struct net_lwip_ctx other = {};
+       struct eth_sandbox_priv *priv;
+       struct udevice *dev;
        char * const argv[] = { "dhcp" };
        char name[256];
        char max_timeout[32];
@@ -195,6 +197,23 @@ static int dhcp_policy_check(struct unit_test_state *uts, 
struct dhcp_policy_tes
                ut_asserteq_str("eth@10003000", env_get("ethact"));
                ut_assertnull(netif_default);
        }
+       /* Runtime restart must retain the wrap signal for BOOTP_MAY_FAIL. */
+       ut_assertok(uclass_get_device(UCLASS_ETH, 1, &dev));
+       priv = dev_get_priv(dev);
+       ut_assertok(env_set("ethact", "eth@10002000"));
+       *test = (struct dhcp_policy_test){
+               .fail_first = true, .bind_after = 1, .ticking = true,
+       };
+       priv->restart_wrap = true;
+       sys_timeout(1, dhcp_policy_tick, test);
+       ret = do_dhcp(NULL, 0, 1, argv);
+       sys_untimeout(dhcp_policy_tick, test);
+       priv->restart_wrap = false;
+       ut_asserteq(IS_ENABLED(CONFIG_BOOTP_MAY_FAIL) ? CMD_RET_FAILURE : 
CMD_RET_SUCCESS,
+                   ret);
+       ut_asserteq(IS_ENABLED(CONFIG_BOOTP_MAY_FAIL) ? 1 : 2, test->packets);
+       ut_asserteq(1, net_restart_wrap);
+       ut_assertnull(netif_default);
        ut_assertok(env_set("ethact", "eth@10002000"));
        ut_assertok(env_set("netretry", "2"));
        ut_assertok(env_set("ethrotate", "no"));

-- 
2.53.0

Reply via email to