From 86f90e2a5fb0283552f610e293fcee2a29724094 Mon Sep 17 00:00:00 2001 From: Yuya Hamamachi Date: Thu, 29 Jan 2026 23:30:18 +0100 Subject: [PATCH 01/16] net: Stop conflating return value with file size in net_loop() The net_loop() currently conflates return value with file size at the end of successful transfer, in NETLOOP_SUCCESS state. The return type of net_loop() is int, which makes this practice workable for file sizes below 2 GiB, but anything above that will lead to overflow and bogus negative return value from net_loop(). The return file size is only used by a few sites in the code base, which can be easily fixed. Change the net_loop() return value to always be only a return code, in case of error the returned value is the error code, in case of successful transfer the value is 0 or 1 instead of 0 or net_boot_file_size . This surely always fits into a signed integer. By keeping the return code 0 or 1 in case of successful transfer, no conditionals which depended on the old behavior are broken, but all the sites had to be inspected and updated accordingly. Fix the few sites which depend on the file size by making them directly use the net_boot_file_size variable value. This variable is accessible to all of those sites already, because they all include net-common.h . Signed-off-by: Yuya Hamamachi Signed-off-by: Marek Vasut --- cmd/mvebu/bubt.c | 2 +- cmd/net.c | 9 +++++---- common/update.c | 10 +++++----- net/net.c | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/cmd/mvebu/bubt.c b/cmd/mvebu/bubt.c index 2755c26cdf7..f8e97d03920 100644 --- a/cmd/mvebu/bubt.c +++ b/cmd/mvebu/bubt.c @@ -661,7 +661,7 @@ static size_t tftp_read_file(const char *file_name) */ image_load_addr = get_load_addr(); ret = net_loop(TFTPGET); - return ret > 0 ? ret : 0; + return ret > 0 ? net_boot_file_size : 0; } static int is_tftp_active(void) diff --git a/cmd/net.c b/cmd/net.c index 24099764493..f6f556f36ae 100644 --- a/cmd/net.c +++ b/cmd/net.c @@ -354,8 +354,8 @@ static int netboot_common(enum proto_t proto, struct cmd_tbl *cmdtp, int argc, char *const argv[]) { char *s; - int rcode = 0; - int size; + int rcode; + u32 size; net_boot_file_name_explicit = false; *net_boot_file_name = '\0'; @@ -396,8 +396,9 @@ static int netboot_common(enum proto_t proto, struct cmd_tbl *cmdtp, int argc, } } - size = net_loop(proto); - if (size < 0) { + rcode = net_loop(proto); + size = net_boot_file_size; + if (rcode < 0) { bootstage_error(BOOTSTAGE_ID_NET_NETLOOP_OK); return CMD_RET_FAILURE; } diff --git a/common/update.c b/common/update.c index 6801b49479d..0bafffede9e 100644 --- a/common/update.c +++ b/common/update.c @@ -32,7 +32,7 @@ static uchar *saved_prot_info; #endif static int update_load(char *filename, ulong msec_max, int cnt_max, ulong addr) { - int size, rv; + int rv; ulong saved_timeout_msecs; int saved_timeout_count; char *saved_netretry, *saved_bootfile; @@ -54,12 +54,12 @@ static int update_load(char *filename, ulong msec_max, int cnt_max, ulong addr) /* download the update file */ image_load_addr = addr; copy_filename(net_boot_file_name, filename, sizeof(net_boot_file_name)); - size = net_loop(TFTPGET); + rv = net_loop(TFTPGET); - if (size < 0) + if (rv < 0) rv = 1; - else if (size > 0) - flush_cache(addr, size); + else + flush_cache(addr, net_boot_file_size); /* restore changed globals and env variable */ tftp_timeout_ms = saved_timeout_msecs; diff --git a/net/net.c b/net/net.c index 8a8160e633f..ae3b977781f 100644 --- a/net/net.c +++ b/net/net.c @@ -692,7 +692,7 @@ restart: eth_set_last_protocol(protocol); - ret = net_boot_file_size; + ret = !!net_boot_file_size; debug_cond(DEBUG_INT_STATE, "--- net_loop Success!\n"); goto done; From a28db0f1ccd6d7f88e9715486376bc039975f72c Mon Sep 17 00:00:00 2001 From: Yuya Hamamachi Date: Thu, 29 Jan 2026 23:30:19 +0100 Subject: [PATCH 02/16] net: tftp: Fix TFTP Transfer Size data type The TFTP transfer size is unsigned integer, update the data type and print formating string accordingly to prevent an overflow in case the file size is longer than 2 GiB. TFTP transfer of a 3 GiB file, before (wrong) and after (right): Loading: ################################################# 16 EiB Loading: ################################################## 3 GiB Signed-off-by: Yuya Hamamachi Signed-off-by: Marek Vasut --- net/tftp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/tftp.c b/net/tftp.c index 78ec44159c1..5f2e0a2bc06 100644 --- a/net/tftp.c +++ b/net/tftp.c @@ -94,7 +94,7 @@ static int tftp_state; static ulong tftp_load_addr; #ifdef CONFIG_TFTP_TSIZE /* The file size reported by the server */ -static int tftp_tsize; +static unsigned int tftp_tsize; /* The number of hashes we printed */ static short tftp_tsize_num_hash; #endif @@ -573,7 +573,7 @@ static void tftp_handler(uchar *pkt, unsigned dest, struct in_addr sip, if (strcasecmp((char *)pkt + i, "tsize") == 0) { tftp_tsize = dectoul((char *)pkt + i + 6, NULL); - debug("size = %s, %d\n", + debug("size = %s, %u\n", (char *)pkt + i + 6, tftp_tsize); } #endif From 337f50bad2ac8e1db126e4f6d372a3186bba2893 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 29 Jan 2026 00:43:45 +0100 Subject: [PATCH 03/16] net: lwip: tftp: add support of tsize option to client 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 Acked-by: Jerome Forissier --- 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 25da952e925..e73bea20e63 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", 0, tftp_state.blksize); + sprintf(payload+2+fname_length+mode_length+tsize_length, "blksize%c%d", 0, tftp_state.blksize); 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 86516e66273..5c3becc68c6 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++; + } + } 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); From 68a8f0f1f34dde836609bf34506d32bfdb2b1f6e Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 29 Jan 2026 01:23:29 +0100 Subject: [PATCH 04/16] net: lwip: wget: rework the '#' printing Currently, the LWIP wget command prints excessive amount of progress indicator '#' for very long file downloads, limit this to one line that scales according to transfer size. The HTTP server does report the size of the entire file in protocol headers, which are received before the actual data transfer. Cache this information and use it to adaptively print progress indicator '#' until it fills one entire line worth of '#', which indicates the transfer has completed. This way, long transfers don't print pages of '#', but every transfer will print exactly one line worth of '#'. The algorithm for '#' printing is the same as TFTP tsize one. Signed-off-by: Marek Vasut Acked-by: Jerome Forissier --- net/lwip/wget.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/net/lwip/wget.c b/net/lwip/wget.c index 55bd2b72e26..008f3b395e7 100644 --- a/net/lwip/wget.c +++ b/net/lwip/wget.c @@ -37,6 +37,8 @@ struct wget_ctx { ulong size; ulong prevsize; ulong start_time; + ulong content_len; + ulong hash_count; enum done_state done; }; @@ -152,6 +154,7 @@ static int store_block(struct wget_ctx *ctx, void *src, u16_t len) { ulong store_addr = ctx->daddr; uchar *ptr; + ulong pos; /* Avoid overflow */ if (wget_info->buffer_size && wget_info->buffer_size < ctx->size + len) @@ -174,10 +177,12 @@ static int store_block(struct wget_ctx *ctx, void *src, u16_t len) ctx->daddr += len; ctx->size += len; - if (ctx->size - ctx->prevsize > PROGRESS_PRINT_STEP_BYTES) { - if (!wget_info->silent) - printf("#"); - ctx->prevsize = ctx->size; + + pos = clamp(ctx->size, 0UL, ctx->content_len); + + while (ctx->hash_count < pos * 50 / ctx->content_len) { + putc('#'); + ctx->hash_count++; } return 0; @@ -234,6 +239,14 @@ static void httpc_result_cb(void *arg, httpc_result_t httpc_result, return; } + /* Print hash marks for the last packet received */ + while (ctx->hash_count < 49) { + putc('#'); + ctx->hash_count++; + } + puts(" "); + print_size(ctx->content_len, ""); + elapsed = get_timer(ctx->start_time); if (!elapsed) elapsed = 1; @@ -263,11 +276,15 @@ static void httpc_result_cb(void *arg, httpc_result_t httpc_result, static err_t httpc_headers_done_cb(httpc_state_t *connection, void *arg, struct pbuf *hdr, u16_t hdr_len, u32_t content_len) { + struct wget_ctx *ctx = arg; + wget_lwip_fill_info(hdr, hdr_len, content_len); if (wget_info->check_buffer_size && (ulong)content_len > wget_info->buffer_size) return ERR_BUF; + ctx->content_len = content_len; + return ERR_OK; } @@ -294,6 +311,8 @@ int wget_do_request(ulong dst_addr, char *uri) ctx.size = 0; ctx.prevsize = 0; ctx.start_time = 0; + ctx.content_len = 0; + ctx.hash_count = 0; if (parse_url(uri, ctx.server_name, &ctx.port, &path, &is_https)) return CMD_RET_USAGE; From 09245e094fe95c92bee0764f31b771b4a07a89c1 Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Thu, 29 Jan 2026 21:01:49 +0000 Subject: [PATCH 05/16] net: dwc_eth_qos: Use lower_32_bits() for tail pointers The DesignWare Cores Ethernet Quality-of-Service databook state that the descriptor address from the start to the end of the ring must not cross the 4GB boundary. Use lower_32_bits() to write the lower 32 bits of descriptor addresses, including the 32-bit tail pointers, consistently. No functional change is intended. Signed-off-by: Jonas Karlman --- drivers/net/dwc_eth_qos.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/dwc_eth_qos.c b/drivers/net/dwc_eth_qos.c index 551ee0ea6a0..74cdaa27644 100644 --- a/drivers/net/dwc_eth_qos.c +++ b/drivers/net/dwc_eth_qos.c @@ -683,7 +683,6 @@ static int eqos_start(struct udevice *dev) int ret, i; ulong rate; u32 val, tx_fifo_sz, rx_fifo_sz, tqs, rqs, pbl; - ulong last_rx_desc; ulong desc_pad; ulong addr64; @@ -1019,8 +1018,8 @@ static int eqos_start(struct udevice *dev) * that's not distinguishable from none of the descriptors being * available. */ - last_rx_desc = (ulong)eqos_get_desc(eqos, EQOS_DESCRIPTORS_RX - 1, true); - writel(last_rx_desc, &eqos->dma_regs->ch0_rxdesc_tail_pointer); + addr64 = (ulong)eqos_get_desc(eqos, EQOS_DESCRIPTORS_RX - 1, true); + writel(lower_32_bits(addr64), &eqos->dma_regs->ch0_rxdesc_tail_pointer); eqos->started = true; @@ -1116,8 +1115,8 @@ static int eqos_send(struct udevice *dev, void *packet, int length) tx_desc->des3 = EQOS_DESC3_OWN | EQOS_DESC3_FD | EQOS_DESC3_LD | length; eqos->config->ops->eqos_flush_desc(tx_desc); - writel((ulong)eqos_get_desc(eqos, eqos->tx_desc_idx, false), - &eqos->dma_regs->ch0_txdesc_tail_pointer); + writel(lower_32_bits((ulong)eqos_get_desc(eqos, eqos->tx_desc_idx, false)), + &eqos->dma_regs->ch0_txdesc_tail_pointer); for (i = 0; i < 1000000; i++) { eqos->config->ops->eqos_inval_desc(tx_desc); @@ -1198,7 +1197,8 @@ static int eqos_free_pkt(struct udevice *dev, uchar *packet, int length) rx_desc->des3 = EQOS_DESC3_OWN | EQOS_DESC3_BUF1V; eqos->config->ops->eqos_flush_desc(rx_desc); } - writel((ulong)rx_desc, &eqos->dma_regs->ch0_rxdesc_tail_pointer); + writel(lower_32_bits((ulong)rx_desc), + &eqos->dma_regs->ch0_rxdesc_tail_pointer); } eqos->rx_desc_idx++; From 34c1ab534c69366d6eeb3d693a9afd37bd14aba5 Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Thu, 29 Jan 2026 21:01:50 +0000 Subject: [PATCH 06/16] net: dwc_eth_qos: Initialize the transmit tail pointer in eqos_start() The DesignWare Cores Ethernet Quality-of-Service databook state that descriptors up to one location less than the one indicated by the descriptor tail pointer are owned by the DMA. The DMA continues to process the descriptors until the following condition occurs: Current Descriptor Pointer == Descriptor Tail Pointer The DMA goes into suspend mode when this condition occurs, and updating the tail pointer resume the DMA processing. Configure the transmit tail pointer to the first (current) descriptor pointer so that the tail pointer is a valid address instead of being initialized to NULL when transmit DMA is started. Also update the receive tail pointer comment to state that by pointing to the last descriptor we are actually implying that all receive descriptors are owned by and can be processed by the DMA. Signed-off-by: Jonas Karlman --- drivers/net/dwc_eth_qos.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/net/dwc_eth_qos.c b/drivers/net/dwc_eth_qos.c index 74cdaa27644..08332210afb 100644 --- a/drivers/net/dwc_eth_qos.c +++ b/drivers/net/dwc_eth_qos.c @@ -1011,12 +1011,17 @@ static int eqos_start(struct udevice *dev) setbits_le32(&eqos->mac_regs->configuration, EQOS_MAC_CONFIGURATION_TE | EQOS_MAC_CONFIGURATION_RE); - /* TX tail pointer not written until we need to TX a packet */ /* - * Point RX tail pointer at last descriptor. Ideally, we'd point at the - * first descriptor, implying all descriptors were available. However, - * that's not distinguishable from none of the descriptors being - * available. + * Point TX tail pointer at the first descriptor, implying no descriptor + * are owned by the DMA. We advance the tail pointer when we need to TX + * a packet in eqos_send(). + */ + addr64 = (ulong)eqos_get_desc(eqos, 0, false); + writel(lower_32_bits(addr64), &eqos->dma_regs->ch0_txdesc_tail_pointer); + + /* + * Point RX tail pointer at the last descriptor, implying all + * descriptors are owned by the DMA. */ addr64 = (ulong)eqos_get_desc(eqos, EQOS_DESCRIPTORS_RX - 1, true); writel(lower_32_bits(addr64), &eqos->dma_regs->ch0_rxdesc_tail_pointer); From 8beb70f2309d6fd6abb1d5c539edab9b0934d656 Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Thu, 29 Jan 2026 21:01:51 +0000 Subject: [PATCH 07/16] net: dwc_eth_qos: Start DMA and MAC after tail pointers are initialized The DesignWare Cores Ethernet Quality-of-Service databook state that receive and transmit descriptor list address and also transmit and receive tail pointer registers should be initialized before the receive and transmit DMAs are started. It also state to enable the MAC receiver only after the DMA is active. Otherwise, received frames can fill the Rx FIFO and overflow. Move the activation of receive and transmit DMA and MAC receiver until after tail pointer registers have been initialized. Signed-off-by: Jonas Karlman --- drivers/net/dwc_eth_qos.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/net/dwc_eth_qos.c b/drivers/net/dwc_eth_qos.c index 08332210afb..b104d4e4725 100644 --- a/drivers/net/dwc_eth_qos.c +++ b/drivers/net/dwc_eth_qos.c @@ -1003,14 +1003,6 @@ static int eqos_start(struct udevice *dev) writel(EQOS_DESCRIPTORS_RX - 1, &eqos->dma_regs->ch0_rxdesc_ring_length); - /* Enable everything */ - setbits_le32(&eqos->dma_regs->ch0_tx_control, - EQOS_DMA_CH0_TX_CONTROL_ST); - setbits_le32(&eqos->dma_regs->ch0_rx_control, - EQOS_DMA_CH0_RX_CONTROL_SR); - setbits_le32(&eqos->mac_regs->configuration, - EQOS_MAC_CONFIGURATION_TE | EQOS_MAC_CONFIGURATION_RE); - /* * Point TX tail pointer at the first descriptor, implying no descriptor * are owned by the DMA. We advance the tail pointer when we need to TX @@ -1026,6 +1018,14 @@ static int eqos_start(struct udevice *dev) addr64 = (ulong)eqos_get_desc(eqos, EQOS_DESCRIPTORS_RX - 1, true); writel(lower_32_bits(addr64), &eqos->dma_regs->ch0_rxdesc_tail_pointer); + /* Enable everything */ + setbits_le32(&eqos->dma_regs->ch0_tx_control, + EQOS_DMA_CH0_TX_CONTROL_ST); + setbits_le32(&eqos->dma_regs->ch0_rx_control, + EQOS_DMA_CH0_RX_CONTROL_SR); + setbits_le32(&eqos->mac_regs->configuration, + EQOS_MAC_CONFIGURATION_TE | EQOS_MAC_CONFIGURATION_RE); + eqos->started = true; debug("%s: OK\n", __func__); From 5fdc297b893eb5cc3e0cfe43359fba9680aa0299 Mon Sep 17 00:00:00 2001 From: Jonas Karlman Date: Thu, 29 Jan 2026 21:01:52 +0000 Subject: [PATCH 08/16] net: dwc_eth_qos: Define more of the unused MAC regs Multicast and Broadcast Queue Enable and Promiscuous Mode Enable bits are currently written to "unused" registers using magic values. Define more of the "unused" MAC regs based on information in the DesignWare Cores Ethernet Quality-of-Service databook. Signed-off-by: Jonas Karlman --- drivers/net/dwc_eth_qos.c | 10 +++++----- drivers/net/dwc_eth_qos.h | 28 ++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/drivers/net/dwc_eth_qos.c b/drivers/net/dwc_eth_qos.c index b104d4e4725..8a396d0b29e 100644 --- a/drivers/net/dwc_eth_qos.c +++ b/drivers/net/dwc_eth_qos.c @@ -890,11 +890,11 @@ static int eqos_start(struct udevice *dev) EQOS_MAC_RXQ_CTRL0_RXQ0EN_SHIFT); /* Multicast and Broadcast Queue Enable */ - setbits_le32(&eqos->mac_regs->unused_0a4, - 0x00100000); - /* enable promise mode */ - setbits_le32(&eqos->mac_regs->unused_004[1], - 0x1); + setbits_le32(&eqos->mac_regs->rxq_ctrl1, + EQOS_MAC_RXQ_CTRL1_MCBCQEN); + /* Promiscuous Mode Enable */ + setbits_le32(&eqos->mac_regs->packet_filter, + EQOS_MAC_PACKET_FILTER_PR); /* Set TX flow control parameters */ /* Set Pause Time */ diff --git a/drivers/net/dwc_eth_qos.h b/drivers/net/dwc_eth_qos.h index c239a5c7aca..ba16f1a37cb 100644 --- a/drivers/net/dwc_eth_qos.h +++ b/drivers/net/dwc_eth_qos.h @@ -14,23 +14,31 @@ #define EQOS_MAC_REGS_BASE 0x000 struct eqos_mac_regs { u32 configuration; /* 0x000 */ - u32 unused_004[(0x070 - 0x004) / 4]; /* 0x004 */ + u32 ext_configuration; /* 0x004 */ + u32 packet_filter; /* 0x008 */ + u32 watchdog_timeout; /* 0x00c */ + u32 unused_010[(0x070 - 0x010) / 4]; /* 0x010 */ u32 q0_tx_flow_ctrl; /* 0x070 */ - u32 unused_070[(0x090 - 0x074) / 4]; /* 0x074 */ + u32 unused_074[(0x090 - 0x074) / 4]; /* 0x074 */ u32 rx_flow_ctrl; /* 0x090 */ - u32 unused_094; /* 0x094 */ + u32 rxq_ctrl4; /* 0x094 */ u32 txq_prty_map0; /* 0x098 */ - u32 unused_09c; /* 0x09c */ + u32 txq_prty_map1; /* 0x09c */ u32 rxq_ctrl0; /* 0x0a0 */ - u32 unused_0a4; /* 0x0a4 */ + u32 rxq_ctrl1; /* 0x0a4 */ u32 rxq_ctrl2; /* 0x0a8 */ - u32 unused_0ac[(0x0dc - 0x0ac) / 4]; /* 0x0ac */ + u32 rxq_ctrl3; /* 0x0ac */ + u32 unused_0b0[(0x0dc - 0x0b0) / 4]; /* 0x0b0 */ u32 us_tic_counter; /* 0x0dc */ - u32 unused_0e0[(0x11c - 0x0e0) / 4]; /* 0x0e0 */ + u32 unused_0e0[(0x110 - 0x0e0) / 4]; /* 0x0e0 */ + u32 version; /* 0x110 */ + u32 debug; /* 0x114 */ + u32 unused_118; /* 0x118 */ u32 hw_feature0; /* 0x11c */ u32 hw_feature1; /* 0x120 */ u32 hw_feature2; /* 0x124 */ - u32 unused_128[(0x200 - 0x128) / 4]; /* 0x128 */ + u32 hw_feature3; /* 0x128 */ + u32 unused_12c[(0x200 - 0x12c) / 4]; /* 0x12c */ u32 mdio_address; /* 0x200 */ u32 mdio_data; /* 0x204 */ u32 unused_208[(0x300 - 0x208) / 4]; /* 0x208 */ @@ -51,6 +59,8 @@ struct eqos_mac_regs { #define EQOS_MAC_CONFIGURATION_TE BIT(1) #define EQOS_MAC_CONFIGURATION_RE BIT(0) +#define EQOS_MAC_PACKET_FILTER_PR BIT(0) + #define EQOS_MAC_Q0_TX_FLOW_CTRL_PT_SHIFT 16 #define EQOS_MAC_Q0_TX_FLOW_CTRL_PT_MASK 0xffff #define EQOS_MAC_Q0_TX_FLOW_CTRL_TFE BIT(1) @@ -66,6 +76,8 @@ struct eqos_mac_regs { #define EQOS_MAC_RXQ_CTRL0_RXQ0EN_ENABLED_DCB 2 #define EQOS_MAC_RXQ_CTRL0_RXQ0EN_ENABLED_AV 1 +#define EQOS_MAC_RXQ_CTRL1_MCBCQEN BIT(20) + #define EQOS_MAC_RXQ_CTRL2_PSRQ0_SHIFT 0 #define EQOS_MAC_RXQ_CTRL2_PSRQ0_MASK 0xff From 1db453a5fee45194be920de6d1b5edf2117ebe1c Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sat, 31 Jan 2026 01:06:23 +0300 Subject: [PATCH 09/16] net: airoha_eth: fix mdio binding to switch device Commit d2145a89bcf6 ("net: airoha: bind MDIO controller on Ethernet load") refers to non-present CONFIG_MDIO_MT7531 and non-present "mt7531-mdio" driver. It should use CONFIG_MDIO_MT7531_MMIO and "mt7531-mdio-mmio" instead. Fixes: d2145a89bcf6 ("net: airoha: bind MDIO controller on Ethernet load") Signed-off-by: Mikhail Kshevetskiy --- drivers/net/Kconfig | 2 +- drivers/net/airoha_eth.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 4fda1b0c28c..47e2fa192c2 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -127,7 +127,7 @@ config AIROHA_ETH select PHYLIB select DEVRES select DM_RESET - select MDIO_MT7531 + select MDIO_MT7531_MMIO help This Driver support Airoha Ethernet QDMA Driver Say Y to enable support for the Airoha Ethernet QDMA. diff --git a/drivers/net/airoha_eth.c b/drivers/net/airoha_eth.c index 046b677d78e..5e393db5ffe 100644 --- a/drivers/net/airoha_eth.c +++ b/drivers/net/airoha_eth.c @@ -989,7 +989,7 @@ static int airoha_eth_bind(struct udevice *dev) struct udevice *mdio_dev; int ret = 0; - if (!CONFIG_IS_ENABLED(MDIO_MT7531)) + if (!CONFIG_IS_ENABLED(MDIO_MT7531_MMIO)) return 0; switch_node = ofnode_by_compatible(ofnode_null(), @@ -1005,7 +1005,7 @@ static int airoha_eth_bind(struct udevice *dev) return 0; } - ret = device_bind_driver_to_node(dev, "mt7531-mdio", "mdio", + ret = device_bind_driver_to_node(dev, "mt7531-mdio-mmio", "mdio", mdio_node, &mdio_dev); if (ret) debug("Warning: failed to bind mdio controller\n"); From caa62920e32a4ea2c134b44265eb19429814b430 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sat, 31 Jan 2026 01:06:24 +0300 Subject: [PATCH 10/16] net: airoha_eth: use proper switch node for en7523 case Commit d2145a89bcf6 ("net: airoha: bind MDIO controller on Ethernet load") uses "airoha,en7581-switch" dts node for finding MDIO childs. This is wrong for EN7523 SoC. The correct node name should be used instead. Fixes: d2145a89bcf6 ("net: airoha: bind MDIO controller on Ethernet load") Signed-off-by: Mikhail Kshevetskiy --- drivers/net/airoha_eth.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/airoha_eth.c b/drivers/net/airoha_eth.c index 5e393db5ffe..3a0ac7ce368 100644 --- a/drivers/net/airoha_eth.c +++ b/drivers/net/airoha_eth.c @@ -985,6 +985,7 @@ static int arht_eth_write_hwaddr(struct udevice *dev) static int airoha_eth_bind(struct udevice *dev) { + struct airoha_eth_soc_data *data = (void *)dev_get_driver_data(dev); ofnode switch_node, mdio_node; struct udevice *mdio_dev; int ret = 0; @@ -993,7 +994,7 @@ static int airoha_eth_bind(struct udevice *dev) return 0; switch_node = ofnode_by_compatible(ofnode_null(), - "airoha,en7581-switch"); + data->switch_compatible); if (!ofnode_valid(switch_node)) { debug("Warning: missing switch node\n"); return 0; From 3aabc0dae03b824ffae06a16a48bf2c89b5c36e7 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sat, 31 Jan 2026 01:06:25 +0300 Subject: [PATCH 11/16] net: mdio-mt7531-mmio: fix switch regs initialization mdio is a child node of the switch, so to get switch base address we need to lookup for a parent node Signed-off-by: Mikhail Kshevetskiy --- drivers/net/mdio-mt7531-mmio.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/mdio-mt7531-mmio.c b/drivers/net/mdio-mt7531-mmio.c index 3e325ca58da..5a0725010f2 100644 --- a/drivers/net/mdio-mt7531-mmio.c +++ b/drivers/net/mdio-mt7531-mmio.c @@ -151,8 +151,13 @@ static const struct mdio_ops mt7531_mdio_ops = { static int mt7531_mdio_probe(struct udevice *dev) { struct mt7531_mdio_priv *priv = dev_get_priv(dev); + ofnode switch_node; - priv->switch_regs = dev_read_addr(dev); + switch_node = ofnode_get_parent(dev_ofnode(dev)); + if (!ofnode_valid(switch_node)) + return -EINVAL; + + priv->switch_regs = ofnode_get_addr(switch_node); if (priv->switch_regs == FDT_ADDR_T_NONE) return -EINVAL; From 0b46bdcf39ddd90ef98b8606cb305e7530f450d7 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sat, 31 Jan 2026 01:06:26 +0300 Subject: [PATCH 12/16] configs: an7581: add mii/mdio support This enables mdio/mii command support. Signed-off-by: Mikhail Kshevetskiy --- configs/an7581_evb_defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/configs/an7581_evb_defconfig b/configs/an7581_evb_defconfig index 73af30cd693..8e2c694dbbb 100644 --- a/configs/an7581_evb_defconfig +++ b/configs/an7581_evb_defconfig @@ -66,6 +66,9 @@ CONFIG_SPI_FLASH_STMICRO=y CONFIG_SPI_FLASH_WINBOND=y CONFIG_SPI_FLASH_MTD=y CONFIG_AIROHA_ETH=y +CONFIG_DM_MDIO=y +CONFIG_CMD_MII=y +CONFIG_CMD_MDIO=y CONFIG_PHY=y CONFIG_PINCTRL=y CONFIG_PINCONF=y From d8bb4a44a27ffd507fb3a0d914101430af19e4c4 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sat, 31 Jan 2026 01:06:27 +0300 Subject: [PATCH 13/16] arm: dts: an7581: add mdio child node to switch node add mdio node to be able see switch port states Signed-off-by: Mikhail Kshevetskiy --- arch/arm/dts/an7581-u-boot.dtsi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/dts/an7581-u-boot.dtsi b/arch/arm/dts/an7581-u-boot.dtsi index a9297ca6503..c5e24c76457 100644 --- a/arch/arm/dts/an7581-u-boot.dtsi +++ b/arch/arm/dts/an7581-u-boot.dtsi @@ -57,6 +57,11 @@ switch: switch@1fb58000 { compatible = "airoha,en7581-switch"; reg = <0 0x1fb58000 0 0x8000>; + + mdio: mdio { + #address-cells = <1>; + #size-cells = <0>; + }; }; snfi: spi@1fa10000 { From d58e12688e1deb283752ffb6ce7c0eb1452e87ab Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sat, 31 Jan 2026 01:06:28 +0300 Subject: [PATCH 14/16] configs: en7523: add mii/mdio support This enables mdio/mii command support. Signed-off-by: Mikhail Kshevetskiy --- configs/en7523_evb_defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/configs/en7523_evb_defconfig b/configs/en7523_evb_defconfig index 113ddb46a7f..ebd99d133c9 100644 --- a/configs/en7523_evb_defconfig +++ b/configs/en7523_evb_defconfig @@ -51,6 +51,9 @@ CONFIG_MTD=y CONFIG_DM_MTD=y CONFIG_MTD_SPI_NAND=y CONFIG_AIROHA_ETH=y +CONFIG_DM_MDIO=y +CONFIG_CMD_MII=y +CONFIG_CMD_MDIO=y CONFIG_PHY=y CONFIG_PINCTRL=y CONFIG_PINCONF=y From b7984ef41a4d6bb65ef430a3c933b3d0b22489e5 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sat, 31 Jan 2026 01:06:29 +0300 Subject: [PATCH 15/16] arm: dts: en7523: add mdio child node to switch node add mdio node to be able see switch port states Signed-off-by: Mikhail Kshevetskiy --- arch/arm/dts/en7523-u-boot.dtsi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/dts/en7523-u-boot.dtsi b/arch/arm/dts/en7523-u-boot.dtsi index f031f81515a..9eadaccc500 100644 --- a/arch/arm/dts/en7523-u-boot.dtsi +++ b/arch/arm/dts/en7523-u-boot.dtsi @@ -42,6 +42,11 @@ switch: switch@1fb58000 { compatible = "airoha,en7523-switch"; reg = <0x1fb58000 0x8000>; + + mdio: mdio { + #address-cells = <1>; + #size-cells = <0>; + }; }; snfi: spi@1fa10000 { From e7b83e64d6bc9f2366669118cb596d75f811eb89 Mon Sep 17 00:00:00 2001 From: Charles Perry Date: Tue, 3 Feb 2026 11:01:40 -0800 Subject: [PATCH 16/16] net: phy: mscc: allow RGMII with internal delay for the VSC8541 Add the missing RGMII modes with internal delay for the VSC8541. Fixes: a5fd13ad1913 ("net: phy: MSCC Add Support for VSC8530-VSC8531-VSC8540-VSC8541") Signed-off-by: Charles Perry --- drivers/net/phy/mscc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/phy/mscc.c b/drivers/net/phy/mscc.c index bd9cd952975..7263887b9ce 100644 --- a/drivers/net/phy/mscc.c +++ b/drivers/net/phy/mscc.c @@ -1371,6 +1371,9 @@ static int vsc8541_config(struct phy_device *phydev) case PHY_INTERFACE_MODE_GMII: case PHY_INTERFACE_MODE_RMII: case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_TXID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_ID: retval = vsc8531_vsc8541_mac_config(phydev); if (retval != 0) return retval;