[PATCH] net: lwip: tftp: add support of tsize option to client
Jerome Forissier
jerome.forissier at arm.com
Thu Jan 29 11:04:16 CET 2026
Hi Marek,
On 29/01/2026 00:43, Marek Vasut wrote:
> The TFTP server can report the size of the entire file that is about to
> be received in the Transfer Size Option, this is described in RFC 2349.
> This functionality is optional and the server may not report tsize in
> case it is not supported.
>
> Always send tsize request to the server to query the transfer size,
> and in case the server does respond, cache that information locally
> in tftp_state.tsize, otherwise cache size 0. Introduce new function
> tftp_client_get_tsize() which returns the cached tftp_state.tsize so
> clients can determine the transfer size and use it.
>
> Update net/lwip/tftp.c to make use of tftp_client_get_tsize() and
> avoid excessive printing of '#' during TFTP transfers in case the
> transfer size is reported by the server.
>
> Submitted upstream: https://savannah.nongnu.org/patch/index.php?item_id=10557
>
> Signed-off-by: Marek Vasut <marek.vasut+renesas at mailbox.org>
Nice feature! One minor comment below.
Acked-by: Jerome Forissier <jerome.forissier at arm.com>
> ---
> Cc: Andrew Goodbody <andrew.goodbody at linaro.org>
> Cc: Heinrich Schuchardt <xypron.glpk at gmx.de>
> Cc: Ilias Apalodimas <ilias.apalodimas at linaro.org>
> Cc: Jerome Forissier <jerome at forissier.org>
> Cc: Joe Hershberger <joe.hershberger at ni.com>
> Cc: Ramon Fried <rfried.dev at gmail.com>
> Cc: Tim Harvey <tharvey at gateworks.com>
> Cc: Tom Rini <trini at konsulko.com>
> Cc: Wolfgang Wallner <wolfgang.wallner at at.abb.com>
> Cc: u-boot at lists.denx.de
> ---
> lib/lwip/lwip/src/apps/tftp/tftp.c | 51 +++++++++++++++----
> .../lwip/src/include/lwip/apps/tftp_client.h | 1 +
> net/lwip/tftp.c | 35 +++++++++++--
> 3 files changed, 74 insertions(+), 13 deletions(-)
>
> diff --git a/lib/lwip/lwip/src/apps/tftp/tftp.c b/lib/lwip/lwip/src/apps/tftp/tftp.c
> index ecb6c55ae11..5061446b1cf 100644
> --- a/lib/lwip/lwip/src/apps/tftp/tftp.c
> +++ b/lib/lwip/lwip/src/apps/tftp/tftp.c
> @@ -98,6 +98,7 @@ struct tftp_state {
> int last_pkt;
> u16_t blknum;
> u16_t blksize;
> + u32_t tsize;
> u8_t retries;
> u8_t mode_write;
> u8_t tftp_mode;
> @@ -167,6 +168,7 @@ send_request(const ip_addr_t *addr, u16_t port, u16_t opcode, const char* fname,
> {
> size_t fname_length = strlen(fname)+1;
> size_t mode_length = strlen(mode)+1;
> + size_t tsize_length = strlen("tsize")+3; /* "tsize\0\0\0" */
> size_t blksize_length = 0;
> int blksize = tftp_state.blksize;
> struct pbuf* p;
> @@ -182,7 +184,7 @@ send_request(const ip_addr_t *addr, u16_t port, u16_t opcode, const char* fname,
> }
> }
>
> - p = init_packet(opcode, 0, fname_length + mode_length + blksize_length - 2);
> + p = init_packet(opcode, 0, fname_length + mode_length + blksize_length + tsize_length - 2);
> if (p == NULL) {
> return ERR_MEM;
> }
> @@ -190,8 +192,9 @@ send_request(const ip_addr_t *addr, u16_t port, u16_t opcode, const char* fname,
> payload = (char*) p->payload;
> MEMCPY(payload+2, fname, fname_length);
> MEMCPY(payload+2+fname_length, mode, mode_length);
> + sprintf(payload+2+fname_length+mode_length, "tsize%c%u%c", 0, 0, 0);
> if (tftp_state.blksize)
> - sprintf(payload+2+fname_length+mode_length, "blksize%c%d%c", 0, tftp_state.blksize, 0);
> + sprintf(payload+2+fname_length+mode_length+tsize_length, "blksize%c%d%c", 0, tftp_state.blksize, 0);
>
> tftp_state.wait_oack = true;
> ret = udp_sendto(tftp_state.upcb, p, addr, port);
> @@ -450,14 +453,24 @@ tftp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr
> }
>
> blknum = lwip_ntohs(sbuf[1]);
> - if (tftp_state.blksize && tftp_state.wait_oack) {
> + if (tftp_state.wait_oack) {
> /*
> - * Data received while we are expecting an OACK for our blksize option.
> + * Data received while we are expecting an OACK for our tsize option.
> * This means the server doesn't support it, let's switch back to the
> * default block size.
> */
> - tftp_state.blksize = 0;
> - tftp_state.wait_oack = false;
> + tftp_state.tsize = 0;
> + tftp_state.wait_oack = false;
> +
> + if (tftp_state.blksize) {
> + /*
> + * Data received while we are expecting an OACK for our blksize option.
> + * This means the server doesn't support it, let's switch back to the
> + * default block size.
> + */
> + tftp_state.blksize = 0;
> + tftp_state.wait_oack = false;
> + }
> }
> if (blknum == tftp_state.blknum) {
> pbuf_remove_header(p, TFTP_HEADER_LENGTH);
> @@ -527,21 +540,31 @@ tftp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr
> }
> break;
> case PP_HTONS(TFTP_OACK): {
> - const char *optval = find_option(p, "blksize");
> + const char *blksizeoptval = find_option(p, "blksize");
> + const char *tsizeoptval = find_option(p, "tsize");
> u16_t srv_blksize = 0;
> + u32_t srv_tsize = 0;
> tftp_state.wait_oack = false;
> - if (optval) {
> + if (blksizeoptval) {
> if (!tftp_state.blksize) {
> /* We did not request this option */
> send_error(addr, port, TFTP_ERROR_ILLEGAL_OPERATION, "blksize unexpected");
> }
> - srv_blksize = atoi(optval);
> + srv_blksize = atoi(blksizeoptval);
> if (srv_blksize <= 0 || srv_blksize > tftp_state.blksize) {
> send_error(addr, port, TFTP_ERROR_ILLEGAL_OPERATION, "Invalid blksize");
> }
> LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: accepting blksize=%d\n", srv_blksize));
> tftp_state.blksize = srv_blksize;
> }
> + if (tsizeoptval) {
> + srv_tsize = atoi(tsizeoptval);
> + if (srv_tsize <= 0) {
> + srv_tsize = 0; /* tsize is optional */
> + }
> + LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: accepting tsize=%d\n", srv_tsize));
> + tftp_state.tsize = srv_tsize;
> + }
> send_ack(addr, port, 0);
> break;
> }
> @@ -655,6 +678,16 @@ tftp_init_client(const struct tftp_context *ctx)
> return tftp_init_common(LWIP_TFTP_MODE_CLIENT, ctx);
> }
>
> +/** @ingroup tftp
> + * Get the transfer size used by the TFTP client. The server may
> + * report zero in case this is unsupported.
> + */
> +u32_t
> +tftp_client_get_tsize(void)
> +{
> + return tftp_state.tsize;
> +}
> +
> /** @ingroup tftp
> * Set the block size to be used by the TFTP client. The server may choose to
> * accept a lower value.
> diff --git a/lib/lwip/lwip/src/include/lwip/apps/tftp_client.h b/lib/lwip/lwip/src/include/lwip/apps/tftp_client.h
> index e1e21d06b67..78de50f4924 100644
> --- a/lib/lwip/lwip/src/include/lwip/apps/tftp_client.h
> +++ b/lib/lwip/lwip/src/include/lwip/apps/tftp_client.h
> @@ -45,6 +45,7 @@ enum tftp_transfer_mode {
>
> err_t tftp_init_client(const struct tftp_context* ctx);
> void tftp_client_set_blksize(u16_t blksize);
> +u32_t tftp_client_get_tsize(void);
> err_t tftp_get(void* handle, const ip_addr_t *addr, u16_t port, const char* fname, enum tftp_transfer_mode mode);
> err_t tftp_put(void* handle, const ip_addr_t *addr, u16_t port, const char* fname, enum tftp_transfer_mode mode);
>
> diff --git a/net/lwip/tftp.c b/net/lwip/tftp.c
> index 6c7ffba661e..213c1292eb1 100644
> --- a/net/lwip/tftp.c
> +++ b/net/lwip/tftp.c
> @@ -31,6 +31,7 @@ struct tftp_ctx {
> ulong daddr;
> ulong size;
> ulong block_count;
> + ulong hash_count;
> ulong start_time;
> enum done_state done;
> };
> @@ -49,6 +50,8 @@ struct tftp_ctx {
> static int store_block(struct tftp_ctx *ctx, void *src, u16_t len)
> {
> ulong store_addr = ctx->daddr;
> + ulong tftp_tsize;
> + ulong pos;
> void *ptr;
>
> if (CONFIG_IS_ENABLED(LMB)) {
> @@ -67,10 +70,21 @@ static int store_block(struct tftp_ctx *ctx, void *src, u16_t len)
> ctx->daddr += len;
> ctx->size += len;
> ctx->block_count++;
> - if (ctx->block_count % 10 == 0) {
> - putc('#');
> - if (ctx->block_count % (65 * 10) == 0)
> - puts("\n\t ");
> +
> + tftp_tsize = tftp_client_get_tsize();
> + if (tftp_tsize) {
> + pos = clamp(ctx->size, 0UL, tftp_tsize);
> +
> + while (ctx->hash_count < pos * 50 / tftp_tsize) {
> + putc('#');
> + ctx->hash_count++;
> + }
Nit: with this format being the same as the non-tsize one, the user has no
way to know how many hashes to expect. How about printing percentages instead?
0%....10%....20%.... etc.
(functional tests may need adjusting though)
> + } else {
> + if (ctx->block_count % 10 == 0) {
> + putc('#');
> + if (ctx->block_count % (65 * 10) == 0)
> + puts("\n\t ");
> + }
> }
>
> return 0;
> @@ -84,6 +98,7 @@ static void *tftp_open(const char *fname, const char *mode, u8_t is_write)
> static void tftp_close(void *handle)
> {
> struct tftp_ctx *ctx = handle;
> + ulong tftp_tsize;
> ulong elapsed;
>
> if (ctx->done == FAILURE || ctx->done == ABORTED) {
> @@ -92,6 +107,17 @@ static void tftp_close(void *handle)
> }
> ctx->done = SUCCESS;
>
> + tftp_tsize = tftp_client_get_tsize();
> + if (tftp_tsize) {
> + /* Print hash marks for the last packet received */
> + while (ctx->hash_count < 49) {
> + putc('#');
> + ctx->hash_count++;
> + }
> + puts(" ");
> + print_size(tftp_tsize, "");
> + }
> +
> elapsed = get_timer(ctx->start_time);
> if (elapsed > 0) {
> puts("\n\t "); /* Line up with "Loading: " */
> @@ -176,6 +202,7 @@ static int tftp_loop(struct udevice *udev, ulong addr, char *fname,
> ctx.done = NOT_DONE;
> ctx.size = 0;
> ctx.block_count = 0;
> + ctx.hash_count = 0;
> ctx.daddr = addr;
>
> printf("Using %s device\n", udev->name);
More information about the U-Boot
mailing list