[PATCH] net: lwip: add tftpsrv command
James Hilliard
james.hilliard1 at gmail.com
Mon Jun 22 22:31:54 CEST 2026
The legacy network stack supports tftpsrv, which listens for an
incoming TFTP write request and receives the first file into memory.
The lwIP stack already builds the lwIP TFTP application, but only wires
it up for client-side tftpboot.
Add a lwIP tftpsrv command and implement the server path with
tftp_init_server(). Reuse the existing lwIP TFTP write callback and
memory copy path so LMB checks, progress output, filesize/fileaddr
updates and EFI bootdev handling stay consistent with tftpboot.
Track receive timeout and write-failure state around the lwIP callbacks
so a stalled or rejected receive is not reported as a successful close.
Move CMD_TFTPSRV out of the legacy-only Kconfig block so it can be
enabled with either network stack.
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
cmd/Kconfig | 14 ++--
cmd/lwip/Makefile | 1 +
cmd/lwip/tftpsrv.c | 11 +++
include/net-lwip.h | 1 +
net/lwip/tftp.c | 181 +++++++++++++++++++++++++++++++++++++++++++--
5 files changed, 197 insertions(+), 11 deletions(-)
create mode 100644 cmd/lwip/tftpsrv.c
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 032e55e8127..8f3d66ddc77 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2127,12 +2127,6 @@ config CMD_TFTPPUT
help
TFTP put command, for uploading files to a server
-config CMD_TFTPSRV
- bool "tftpsrv"
- depends on CMD_TFTPBOOT
- help
- Act as a TFTP server and boot the first received file
-
config NET_TFTP_VARS
bool "Control TFTP timeout and count through environment"
depends on CMD_TFTPBOOT
@@ -2279,6 +2273,14 @@ config CMD_TFTPBOOT
help
tftpboot - load file via network using TFTP protocol
+config CMD_TFTPSRV
+ bool "tftpsrv"
+ depends on CMD_TFTPBOOT
+ help
+ Act as a TFTP server and receive the first incoming file into
+ memory. The command returns successfully after the transfer so
+ boot scripts can boot the received image from the load address.
+
config CMD_WGET
bool "wget"
default y if SANDBOX || ARCH_QEMU
diff --git a/cmd/lwip/Makefile b/cmd/lwip/Makefile
index 90df1f5511c..245683a9672 100644
--- a/cmd/lwip/Makefile
+++ b/cmd/lwip/Makefile
@@ -4,4 +4,5 @@ obj-$(CONFIG_CMD_NFS) += nfs.o
obj-$(CONFIG_CMD_PING) += ping.o
obj-$(CONFIG_CMD_SNTP) += sntp.o
obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o
+obj-$(CONFIG_CMD_TFTPSRV) += tftpsrv.o
obj-$(CONFIG_CMD_WGET) += wget.o
diff --git a/cmd/lwip/tftpsrv.c b/cmd/lwip/tftpsrv.c
new file mode 100644
index 00000000000..522a7ccacdf
--- /dev/null
+++ b/cmd/lwip/tftpsrv.c
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <command.h>
+#include <net.h>
+
+U_BOOT_CMD(tftpsrv, 2, 1, do_tftpsrv,
+ "act as a TFTP server and boot the first received file",
+ "[loadAddress]\n"
+ "Listen for an incoming TFTP transfer, receive a file and boot it.\n"
+ "The transfer is aborted if a transfer has not been started after\n"
+ "about 50 seconds or if Ctrl-C is pressed.");
diff --git a/include/net-lwip.h b/include/net-lwip.h
index 20cb0992cce..571d8941ff0 100644
--- a/include/net-lwip.h
+++ b/include/net-lwip.h
@@ -52,6 +52,7 @@ bool wget_validate_uri(char *uri);
int do_dns(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
int do_nfs(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
+int do_tftpsrv(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
int do_wget(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]);
#endif /* __NET_LWIP_H__ */
diff --git a/net/lwip/tftp.c b/net/lwip/tftp.c
index 7f3b28b8507..af25aabc973 100644
--- a/net/lwip/tftp.c
+++ b/net/lwip/tftp.c
@@ -11,6 +11,7 @@
#include <linux/delay.h>
#include <linux/kconfig.h>
#include <lwip/apps/tftp_client.h>
+#include <lwip/apps/tftp_server.h>
#include <lwip/timeouts.h>
#include <mapmem.h>
#include <net.h>
@@ -19,6 +20,8 @@
#define PROGRESS_PRINT_STEP_BYTES (10 * 1024)
/* Max time to wait for first data packet from server */
#define NO_RSP_TIMEOUT_MS 10000
+/* Max time to wait for an incoming TFTP write request */
+#define NO_WRQ_TIMEOUT_MS (TFTP_TIMEOUT_MSECS * TFTP_MAX_RETRIES)
enum done_state {
NOT_DONE = 0,
@@ -34,8 +37,27 @@ struct tftp_ctx {
ulong hash_count;
ulong start_time;
enum done_state done;
+ bool is_server;
+ bool wrq_accepted;
+ char fname[TFTP_MAX_FILENAME_LEN + 1];
};
+static struct tftp_ctx *tftpsrv_active_ctx;
+
+static void transfer_timeout(void *arg)
+{
+ struct tftp_ctx *ctx = (struct tftp_ctx *)arg;
+
+ printf("Timeout!\n");
+ ctx->done = FAILURE;
+}
+
+static void restart_transfer_timeout(struct tftp_ctx *ctx)
+{
+ sys_untimeout(transfer_timeout, ctx);
+ sys_timeout(TFTP_TIMEOUT_MSECS, transfer_timeout, ctx);
+}
+
/**
* store_block() - copy received data
*
@@ -71,7 +93,7 @@ static int store_block(struct tftp_ctx *ctx, void *src, u16_t len)
ctx->size += len;
ctx->block_count++;
- tftp_tsize = tftp_client_get_tsize();
+ tftp_tsize = ctx->is_server ? 0 : tftp_client_get_tsize();
if (tftp_tsize) {
pos = clamp(ctx->size, 0UL, tftp_tsize);
@@ -92,7 +114,20 @@ static int store_block(struct tftp_ctx *ctx, void *src, u16_t len)
static void *tftp_open(const char *fname, const char *mode, u8_t is_write)
{
- return NULL;
+ struct tftp_ctx *ctx = tftpsrv_active_ctx;
+
+ if (!IS_ENABLED(CONFIG_CMD_TFTPSRV) || !ctx || !is_write)
+ return NULL;
+
+ ctx->wrq_accepted = true;
+ ctx->start_time = get_timer(0);
+ snprintf(ctx->fname, sizeof(ctx->fname), "%s", fname);
+ restart_transfer_timeout(ctx);
+
+ printf("\nReceiving '%s' mode '%s'\n", fname, mode);
+ puts("Loading: ");
+
+ return ctx;
}
static void tftp_close(void *handle)
@@ -101,13 +136,15 @@ static void tftp_close(void *handle)
ulong tftp_tsize;
ulong elapsed;
+ sys_untimeout(transfer_timeout, ctx);
+
if (ctx->done == FAILURE || ctx->done == ABORTED) {
/* Closing after an error or Ctrl-C */
return;
}
ctx->done = SUCCESS;
- tftp_tsize = tftp_client_get_tsize();
+ tftp_tsize = ctx->is_server ? 0 : tftp_client_get_tsize();
if (tftp_tsize) {
/* Print hash marks for the last packet received */
while (ctx->hash_count < 49) {
@@ -142,9 +179,14 @@ static int tftp_write(void *handle, struct pbuf *p)
struct tftp_ctx *ctx = handle;
struct pbuf *q;
- for (q = p; q; q = q->next)
- if (store_block(ctx, q->payload, q->len) < 0)
+ for (q = p; q; q = q->next) {
+ if (store_block(ctx, q->payload, q->len) < 0) {
+ ctx->done = FAILURE;
return -1;
+ }
+ }
+
+ restart_transfer_timeout(ctx);
return 0;
}
@@ -204,6 +246,9 @@ static int tftp_loop(struct udevice *udev, ulong addr, char *fname,
ctx.block_count = 0;
ctx.hash_count = 0;
ctx.daddr = addr;
+ ctx.is_server = false;
+ ctx.wrq_accepted = false;
+ ctx.fname[0] = '\0';
printf("Using %s device\n", udev->name);
printf("TFTP from server %s; our IP address is %s\n",
@@ -258,6 +303,132 @@ static int tftp_loop(struct udevice *udev, ulong addr, char *fname,
return -1;
}
+static void no_request(void *arg)
+{
+ struct tftp_ctx *ctx = (struct tftp_ctx *)arg;
+
+ if (ctx->wrq_accepted)
+ return;
+
+ printf("Timeout!\n");
+ ctx->done = FAILURE;
+}
+
+static int tftpsrv_loop(struct udevice *udev, ulong addr)
+{
+ struct netif *netif;
+ struct tftp_ctx ctx;
+ const char *ipaddr;
+ err_t err;
+
+ if (addr == 0)
+ return -1;
+
+ ipaddr = env_get("ipaddr");
+ if (!ipaddr || !*ipaddr) {
+ log_err("error: ipaddr has to be set\n");
+ return -1;
+ }
+
+ netif = net_lwip_new_netif(udev);
+ if (!netif)
+ return -1;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.done = NOT_DONE;
+ ctx.daddr = addr;
+ ctx.is_server = true;
+
+ printf("Using %s device\n", udev->name);
+ printf("Listening for TFTP transfer on %s\n", ipaddr);
+ printf("Load address: 0x%lx\n", ctx.daddr);
+
+ tftpsrv_active_ctx = &ctx;
+ err = tftp_init_server(&tftp_context);
+ if (err != ERR_OK) {
+ log_err("tftp_init_server err: %d\n", err);
+ tftpsrv_active_ctx = NULL;
+ net_lwip_remove_netif(netif);
+ return -1;
+ }
+
+ ctx.start_time = get_timer(0);
+ sys_timeout(NO_WRQ_TIMEOUT_MS, no_request, &ctx);
+ while (!ctx.done) {
+ net_lwip_rx(udev, netif);
+ if (ctrlc()) {
+ printf("\nAbort\n");
+ ctx.done = ABORTED;
+ break;
+ }
+ }
+ sys_untimeout(no_request, (void *)&ctx);
+
+ tftp_cleanup();
+ tftpsrv_active_ctx = NULL;
+ net_lwip_remove_netif(netif);
+
+ if (ctx.done == SUCCESS) {
+ if (env_set_hex("fileaddr", addr)) {
+ log_err("fileaddr not updated\n");
+ return -1;
+ }
+ efi_set_bootdev("Net", "", ctx.fname, map_sysmem(addr, 0),
+ ctx.size);
+ return 0;
+ }
+
+ return -1;
+}
+
+int do_tftpsrv(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+{
+ int ret = CMD_RET_SUCCESS;
+ char *end;
+ ulong laddr;
+ ulong addr;
+
+ if (!IS_ENABLED(CONFIG_CMD_TFTPSRV))
+ return CMD_RET_FAILURE;
+
+ laddr = env_get_ulong("loadaddr", 16, image_load_addr);
+
+ switch (argc) {
+ case 1:
+ break;
+ case 2:
+ addr = hextoul(argv[1], &end);
+ if (end == argv[1] || *end) {
+ ret = CMD_RET_USAGE;
+ goto out;
+ }
+ laddr = addr;
+ break;
+ default:
+ ret = CMD_RET_USAGE;
+ goto out;
+ }
+
+ if (!laddr) {
+ log_err("error: no load address\n");
+ ret = CMD_RET_FAILURE;
+ goto out;
+ }
+
+ if (net_lwip_eth_start() < 0) {
+ ret = CMD_RET_FAILURE;
+ goto out;
+ }
+
+ if (tftpsrv_loop(eth_get_dev(), laddr) < 0)
+ ret = CMD_RET_FAILURE;
+ else
+ image_load_addr = laddr;
+
+out:
+ return ret;
+}
+
int do_tftpb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
int ret = CMD_RET_SUCCESS;
--
2.53.0
More information about the U-Boot
mailing list