From 614ebea14bfc4f015a739e0d7b047fe98b831d37 Mon Sep 17 00:00:00 2001 From: Markus Niebel Date: Mon, 1 Jun 2026 11:02:16 +0200 Subject: net: phy: dp83867: enable extended read / write for driver Add a wrapper to implement ext_read / write using phy_[read,write]_mmd. Check if devad is the only supported extended MMD address on this PHY. Signed-off-by: Markus Niebel Signed-off-by: Alexander Stein --- drivers/net/phy/dp83867.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c index ebed61de133..34c871857e1 100644 --- a/drivers/net/phy/dp83867.c +++ b/drivers/net/phy/dp83867.c @@ -126,6 +126,22 @@ struct dp83867_private { bool sgmii_ref_clk_en; }; +static int dp83867_phy_extread(struct phy_device *phydev, + int addr, int devad, int reg) +{ + if (devad != DP83867_DEVADDR) + return -EINVAL; + return phy_read_mmd(phydev, devad, addr); +}; + +static int dp83867_phy_extwrite(struct phy_device *phydev, int addr, + int devad, int reg, u16 val) +{ + if (devad != DP83867_DEVADDR) + return -EINVAL; + return phy_write_mmd(phydev, devad, addr, (u32)val); +}; + static int dp83867_config_port_mirroring(struct phy_device *phydev) { struct dp83867_private *dp83867 = @@ -410,4 +426,6 @@ U_BOOT_PHY_DRIVER(dp83867) = { .config = &dp83867_config, .startup = &genphy_startup, .shutdown = &genphy_shutdown, + .readext = dp83867_phy_extread, + .writeext = dp83867_phy_extwrite, }; -- cgit v1.3.1 From 2671bd1d77abdbf0b52a52e1419e01543476143b Mon Sep 17 00:00:00 2001 From: Pranav Tilak Date: Wed, 10 Jun 2026 16:09:57 +0530 Subject: net: phy: fix duplicate eth_phy binding When both CONFIG_PHY_ETHERNET_ID and CONFIG_DM_ETH_PHY are enabled, eth_phy_binds_nodes() called from eth_post_bind() already binds the ethernet PHY node to eth_phy_generic_drv. However, phy_connect_phy_id() called via phy_connect() also binds the same PHY node, resulting in duplicate entries in the DM tree. Fix this by introducing phy_connect_dm_bound() which checks if the PHY is already bound via uclass_find_device_by_phandle(). If so, it gets the phy_device via phy_find_by_mask() since the udevice does not store a phy_device pointer and the phy_device can only be obtained by scanning the MDIO bus. The phydev->node is then set from the already-bound DM device. This skips the generic binding methods in phy_connect() when the PHY is already DM-bound. Fixes: 68a4d1506109 ("net: phy: Bind ETH_PHY uclass driver to each new PHY") Signed-off-by: Pranav Tilak --- drivers/net/phy/phy.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index d7e0c4fe02d..f2e9b2a9820 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -921,6 +922,27 @@ static struct phy_device *phy_connect_fixed(struct mii_dev *bus, } #endif +#ifdef CONFIG_DM_ETH_PHY +static struct phy_device *phy_connect_dm_bound(struct mii_dev *bus, + struct udevice *dev, + uint mask) +{ + struct udevice *dm_phy_dev; + struct phy_device *phydev; + + if (!uclass_find_device_by_phandle(UCLASS_ETH_PHY, dev, + "phy-handle", &dm_phy_dev)) { + phydev = phy_find_by_mask(bus, mask); + if (phydev) + phydev->node = dev_ofnode(dm_phy_dev); + + return phydev; + } + + return NULL; +} +#endif + struct phy_device *phy_connect(struct mii_dev *bus, int addr, struct udevice *dev, phy_interface_t interface) @@ -928,8 +950,13 @@ struct phy_device *phy_connect(struct mii_dev *bus, int addr, struct phy_device *phydev = NULL; uint mask = (addr >= 0) ? (1 << addr) : 0xffffffff; +#ifdef CONFIG_DM_ETH_PHY + phydev = phy_connect_dm_bound(bus, dev, mask); +#endif + #ifdef CONFIG_PHY_FIXED - phydev = phy_connect_fixed(bus, dev); + if (!phydev) + phydev = phy_connect_fixed(bus, dev); #endif #ifdef CONFIG_PHY_NCSI -- cgit v1.3.1 From afcd55654d858f8f6c92847e895650c443c5f8ec Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 3 Jul 2026 15:50:19 +0800 Subject: net: Drop unnecessary device_set_name When device was created, it already has a name assigned. There is no need to alloc space for name and set it with same device name. Cleanup the code to avoid unnecessary device_set_name. Signed-off-by: Peng Fan Reviewed-by: Jerome Forissier --- drivers/net/dm9000x.c | 6 ------ drivers/net/ftmac100.c | 6 ------ drivers/net/ks8851_mll.c | 6 ------ drivers/net/smc911x.c | 6 ------ 4 files changed, 24 deletions(-) diff --git a/drivers/net/dm9000x.c b/drivers/net/dm9000x.c index 9e17f0b9c28..53225979b66 100644 --- a/drivers/net/dm9000x.c +++ b/drivers/net/dm9000x.c @@ -638,11 +638,6 @@ static int dm9000_read_rom_hwaddr(struct udevice *dev) return !is_valid_ethaddr(pdata->enetaddr); } -static int dm9000_bind(struct udevice *dev) -{ - return device_set_name(dev, dev->name); -} - static int dm9000_of_to_plat(struct udevice *dev) { struct dm9000_priv *db = dev_get_priv(dev); @@ -673,7 +668,6 @@ U_BOOT_DRIVER(dm9000) = { .name = "eth_dm9000", .id = UCLASS_ETH, .of_match = dm9000_ids, - .bind = dm9000_bind, .of_to_plat = dm9000_of_to_plat, .ops = &dm9000_ops, .priv_auto = sizeof(struct dm9000_priv), diff --git a/drivers/net/ftmac100.c b/drivers/net/ftmac100.c index fa0b3dbb6d1..d9826178e82 100644 --- a/drivers/net/ftmac100.c +++ b/drivers/net/ftmac100.c @@ -429,11 +429,6 @@ static int ftmac100_remove(struct udevice *dev) return 0; } -static int ftmac100_bind(struct udevice *dev) -{ - return device_set_name(dev, dev->name); -} - static const struct eth_ops ftmac100_ops = { .start = ftmac100_start, .send = ftmac100_send, @@ -451,7 +446,6 @@ U_BOOT_DRIVER(ftmac100) = { .name = "ftmac100", .id = UCLASS_ETH, .of_match = ftmac100_ids, - .bind = ftmac100_bind, .of_to_plat = ftmac100_of_to_plat, .probe = ftmac100_probe, .remove = ftmac100_remove, diff --git a/drivers/net/ks8851_mll.c b/drivers/net/ks8851_mll.c index 2c956154d09..c2f87d4222b 100644 --- a/drivers/net/ks8851_mll.c +++ b/drivers/net/ks8851_mll.c @@ -571,11 +571,6 @@ static int ks8851_read_rom_hwaddr(struct udevice *dev) return !is_valid_ethaddr(pdata->enetaddr); } -static int ks8851_bind(struct udevice *dev) -{ - return device_set_name(dev, dev->name); -} - static int ks8851_probe(struct udevice *dev) { struct ks_net *ks = dev_get_priv(dev); @@ -615,7 +610,6 @@ U_BOOT_DRIVER(ks8851) = { .name = "eth_ks8851", .id = UCLASS_ETH, .of_match = ks8851_ids, - .bind = ks8851_bind, .of_to_plat = ks8851_of_to_plat, .probe = ks8851_probe, .ops = &ks8851_ops, diff --git a/drivers/net/smc911x.c b/drivers/net/smc911x.c index f39ba40944f..2835eb45856 100644 --- a/drivers/net/smc911x.c +++ b/drivers/net/smc911x.c @@ -431,11 +431,6 @@ static int smc911x_read_rom_hwaddr(struct udevice *dev) return 0; } -static int smc911x_bind(struct udevice *dev) -{ - return device_set_name(dev, dev->name); -} - static int smc911x_probe(struct udevice *dev) { struct smc911x_priv *priv = dev_get_priv(dev); @@ -487,7 +482,6 @@ U_BOOT_DRIVER(smc911x) = { .name = "eth_smc911x", .id = UCLASS_ETH, .of_match = smc911x_ids, - .bind = smc911x_bind, .of_to_plat = smc911x_of_to_plat, .probe = smc911x_probe, .ops = &smc911x_ops, -- cgit v1.3.1 From 0601d2d9a2908d92cfad23f6a1dfcf1eaccad8b7 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Wed, 8 Jul 2026 13:31:17 -0600 Subject: net: lwip: add tftpsrv command The legacy network stack supports tftpsrv, which listens for an incoming TFTP write request and receives the first file into memory. Despite the old command help wording, the command returns after receiving the file and does not boot it automatically. 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. Update the command help text and add usage documentation for the receive-only behavior. Add pytest coverage for tftpsrv using a generated host file and curl's TFTP upload support. Enable the command in qemu_arm64_lwip_defconfig so the test can be run with the existing lwIP QEMU build when the boardenv provides env__net_tftpsrv_file. Signed-off-by: James Hilliard [Jerome Forissier: remove trailing ':' after SPDX tag] Signed-off-by: Jerome Forissier Reviewed-by: Jerome Forissier --- cmd/Kconfig | 14 +-- cmd/lwip/Makefile | 1 + cmd/lwip/tftpsrv.c | 11 +++ cmd/net.c | 4 +- configs/qemu_arm64_lwip_defconfig | 1 + doc/usage/cmd/tftpsrv.rst | 73 +++++++++++++++ include/net-lwip.h | 1 + lib/lwip/u-boot/lwipopts.h | 11 +++ net/lwip/tftp.c | 188 +++++++++++++++++++++++++++++++++++++- test/py/tests/test_net.py | 94 +++++++++++++++++++ 10 files changed, 385 insertions(+), 13 deletions(-) create mode 100644 cmd/lwip/tftpsrv.c create mode 100644 doc/usage/cmd/tftpsrv.rst diff --git a/cmd/Kconfig b/cmd/Kconfig index 35ce42f8397..ff90a87024c 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -2135,12 +2135,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 @@ -2287,6 +2281,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..6370c900489 --- /dev/null +++ b/cmd/lwip/tftpsrv.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include + +U_BOOT_CMD(tftpsrv, 2, 1, do_tftpsrv, + "act as a TFTP server and receive the first file", + "[loadAddress]\n" + "Listen for an incoming TFTP transfer and receive a file into memory.\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/cmd/net.c b/cmd/net.c index f6f556f36ae..0f0b2386d87 100644 --- a/cmd/net.c +++ b/cmd/net.c @@ -89,9 +89,9 @@ static int do_tftpsrv(struct cmd_tbl *cmdtp, int flag, int argc, U_BOOT_CMD( tftpsrv, 2, 1, do_tftpsrv, - "act as a TFTP server and boot the first received file", + "act as a TFTP server and receive the first file", "[loadAddress]\n" - "Listen for an incoming TFTP transfer, receive a file and boot it.\n" + "Listen for an incoming TFTP transfer and receive a file into memory.\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/configs/qemu_arm64_lwip_defconfig b/configs/qemu_arm64_lwip_defconfig index a974970c3d3..06ed5a131db 100644 --- a/configs/qemu_arm64_lwip_defconfig +++ b/configs/qemu_arm64_lwip_defconfig @@ -7,6 +7,7 @@ CONFIG_NET_LWIP=y CONFIG_CMD_DNS=y CONFIG_CMD_NFS=y CONFIG_CMD_SNTP=y +CONFIG_CMD_TFTPSRV=y CONFIG_CMD_WGET=y CONFIG_EFI_HTTP_BOOT=y CONFIG_WGET_HTTPS=y diff --git a/doc/usage/cmd/tftpsrv.rst b/doc/usage/cmd/tftpsrv.rst new file mode 100644 index 00000000000..d067afdba57 --- /dev/null +++ b/doc/usage/cmd/tftpsrv.rst @@ -0,0 +1,73 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. index:: + single: tftpsrv (command) + +tftpsrv command +=============== + +Synopsis +-------- + +:: + + tftpsrv [loadAddress] + +Description +----------- + +The tftpsrv command listens for an incoming TFTP write request and receives +the first transferred file into memory. + +loadAddress + memory address where the received file is stored. If not provided, the + address is taken from the *loadaddr* environment variable or the default + image load address. + +After a successful transfer, the *fileaddr* and *filesize* environment +variables describe the received file. The command returns successfully after +the transfer has completed. It does not boot the file automatically; boot +scripts can use commands such as bootm, booti or bootefi to boot from the +load address. + +The transfer is aborted if no transfer has started after about 50 seconds or +if Ctrl-C is pressed. + +Example +------- + +In the example the following steps are executed: + +* setup the board network address +* receive a FIT image from a host +* boot the received FIT image + +:: + + => setenv autoload no + => dhcp + BOOTP broadcast 1 + DHCP client bound to address 192.168.1.40 (7 ms) + => tftpsrv $loadaddr + Using ethernet@1c30000 device + Listening for TFTP transfer on 192.168.1.40 + Load address: 0x42000000 + Loading: ################################################################# + 6.5 MiB/s + done + Bytes transferred = 1048576 (100000 hex) + => bootm $fileaddr + +On the host, send the file to the board while U-Boot is listening: + +:: + + $ curl --upload-file image.fit tftp://192.168.1.40/image.fit + +Configuration +------------- + +The command is only available if CONFIG_CMD_TFTPSRV=y. + +The command is supported by both the legacy network stack and the lwIP network +stack. diff --git a/include/net-lwip.h b/include/net-lwip.h index 5d0627eb271..8e59a2299e0 100644 --- a/include/net-lwip.h +++ b/include/net-lwip.h @@ -53,6 +53,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/lib/lwip/u-boot/lwipopts.h b/lib/lwip/u-boot/lwipopts.h index 8dae004f1a2..1c1adc57c6b 100644 --- a/lib/lwip/u-boot/lwipopts.h +++ b/lib/lwip/u-boot/lwipopts.h @@ -72,6 +72,17 @@ #define PBUF_POOL_RESERVE 4 #define TFTP_BLOCKSIZE_THRESHOLD 4096 +/* + * Match the legacy U-Boot TFTP filename buffer. The legacy + * CONFIG_TFTP_FILE_NAME_MAX_LEN value is a buffer size including the trailing + * NUL, while lwIP's TFTP_MAX_FILENAME_LEN is the usable string length. + */ +#ifdef CONFIG_TFTP_FILE_NAME_MAX_LEN +#define TFTP_MAX_FILENAME_LEN (CONFIG_TFTP_FILE_NAME_MAX_LEN - 1) +#else +#define TFTP_MAX_FILENAME_LEN 127 +#endif + #if defined(CONFIG_TFTP_BLOCKSIZE) && (CONFIG_TFTP_BLOCKSIZE > TFTP_BLOCKSIZE_THRESHOLD) #define PBUF_POOL_SIZE (((CONFIG_TFTP_BLOCKSIZE + (IP_FRAG_MTU_USABLE - 1)) / \ IP_FRAG_MTU_USABLE) + PBUF_POOL_HEADROOM) diff --git a/net/lwip/tftp.c b/net/lwip/tftp.c index 571c38172f9..d6a9f29a260 100644 --- a/net/lwip/tftp.c +++ b/net/lwip/tftp.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -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 TFTPSRV_LISTEN_TIMEOUT_MS 50000 enum done_state { NOT_DONE = 0, @@ -34,8 +37,31 @@ 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]; }; +/* + * The lwIP TFTP server open callback has no user-data argument. Keep the + * current server context here so tftp_open() can return it. + */ +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 +97,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 +118,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 +140,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 +183,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 +250,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 +307,135 @@ 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; + int ret = -1; + 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); + goto out_remove_netif; + } + + ctx.start_time = get_timer(0); + sys_timeout(TFTPSRV_LISTEN_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, &ctx); + sys_untimeout(transfer_timeout, &ctx); + + tftp_cleanup(); + + if (ctx.done == SUCCESS) { + if (env_set_hex("fileaddr", addr)) { + log_err("fileaddr not updated\n"); + goto out_remove_netif; + } + efi_set_bootdev("Net", "", ctx.fname, map_sysmem(addr, 0), + ctx.size); + ret = 0; + } + +out_remove_netif: + tftpsrv_active_ctx = NULL; + net_lwip_remove_netif(netif); + + return ret; +} + +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; + net_lwip_eth_stop(); + +out: + return ret; +} + int do_tftpb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { int ret = CMD_RET_SUCCESS; diff --git a/test/py/tests/test_net.py b/test/py/tests/test_net.py index 27cdd73fd49..a2007c2fd3a 100644 --- a/test/py/tests/test_net.py +++ b/test/py/tests/test_net.py @@ -59,6 +59,19 @@ For example: 'fnu': 'ubtest-upload.bin', } + # Details regarding a file that may be written to U-Boot using the tftpsrv + # command. This variable may be omitted or set to None if tftpsrv testing + # is not possible or desired. The test uses host-side curl TFTP support to + # upload a generated file to U-Boot. The optional tftpsrv_url entry may be + # used when the host must use a forwarded address instead of U-Boot's + # ipaddr value. + env__net_tftpsrv_file = { + 'fn': 'ubtest-tftpsrv.bin', + 'addr': 0x10000000, + 'size': 4096, + 'timeout': 50000, + } + # Details regarding a file that may be read from a NFS server. This variable # may be omitted or set to None if NFS testing is not possible or desired. env__net_nfs_readable_file = { @@ -89,6 +102,8 @@ import utils import uuid import datetime import re +import tempfile +import zlib net_set_up = False net6_set_up = False @@ -460,3 +475,82 @@ def test_net_tftpput(ubman): output = ubman.run_command("crc32 $fileaddr $filesize") assert expected_tftpb_crc in output + + +@pytest.mark.buildconfigspec("cmd_crc32") +@pytest.mark.buildconfigspec("cmd_tftpsrv") +@pytest.mark.requiredtool("curl") +def test_net_tftpsrv(ubman): + """Test the tftpsrv command. + + A file is generated on the host, uploaded to U-Boot using TFTP and then + validated in U-Boot using its size and CRC32. + + The details of the file to upload are provided by the boardenv_* file; + see the comment at the beginning of this file. + """ + + if not net_set_up: + pytest.skip("Network not initialized") + + f = ubman.config.env.get("env__net_tftpsrv_file", None) + if not f: + pytest.skip("No tftpsrv file to write") + + curl_version = utils.run_and_log(ubman, ["curl", "--version"]) + if "tftp" not in curl_version.split(): + pytest.skip("curl does not support TFTP") + + addr = f.get("addr", None) + if not addr: + addr = utils.find_ram_base(ubman) + + timeout = f.get("timeout", ubman.p.timeout) + timeout_secs = max(1, (timeout + 999) // 1000) + size = f.get("size", 4096) + fn = f.get("fn", "ubtest-tftpsrv.bin") + url = f.get("tftpsrv_url", None) + data = bytes([i % 251 for i in range(size)]) + crc = "%08x" % (zlib.crc32(data) & 0xffffffff) + + ip = ubman.run_command("echo $ipaddr").strip() + if not ip: + pytest.skip("No U-Boot IP address") + if not url: + url = "tftp://%s/%s" % (ip, fn) + + with tempfile.NamedTemporaryFile() as tmp: + tmp.write(data) + tmp.flush() + + done = False + with ubman.temporary_timeout(timeout): + try: + ubman.run_command("tftpsrv %x" % addr, + wait_for_prompt=False) + ubman.wait_for("Listening for TFTP transfer") + utils.run_and_log( + ubman, + [ + "curl", + "--fail", + "--max-time", + str(timeout_secs), + "--upload-file", + tmp.name, + url, + ], + ) + ubman.wait_for("Bytes transferred = %d" % size) + ubman.wait_for(ubman.prompt) + done = True + finally: + if not done: + ubman.ctrlc() + ubman.drain_console() + + output = ubman.run_command("echo $filesize") + assert "%x" % size in output + + output = ubman.run_command("crc32 $fileaddr $filesize") + assert crc in output -- cgit v1.3.1 From 02154ba0367a4baebf5dc72fa1a419d516f242a6 Mon Sep 17 00:00:00 2001 From: Weijie Gao Date: Wed, 15 Jul 2026 16:42:36 +0800 Subject: net: lwip: handle chained pbufs in transmit path LwIP may pass a packet to the netif linkoutput callback as a chain of pbufs. In this case, p->len only describes the length of the first pbuf, while p->tot_len describes the length of the whole packet. The current transmit path only sends the first pbuf. This can truncate packets whose headers have already been generated for the full packet length, resulting in malformed frames on the wire. For example, the IP header may record a larger total length than the actual Ethernet frame length. Assemble chained pbufs into one aligned contiguous buffer before passing the packet to the Ethernet driver. Fixes: 98ad145db61a (net: lwip: add DHCP support and dhcp commmand) Signed-off-by: Weijie Gao Reviewed-by: Jerome Forissier --- net/lwip/net-lwip.c | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/net/lwip/net-lwip.c b/net/lwip/net-lwip.c index cfe5a6a640d..8f8f9d69020 100644 --- a/net/lwip/net-lwip.c +++ b/net/lwip/net-lwip.c @@ -40,28 +40,47 @@ char *pxelinux_configfile; static err_t net_lwip_tx(struct netif *netif, struct pbuf *p) { struct udevice *udev = netif->state; - void *pp = NULL; + bool pp_allocated = false; + u32 plen; + void *pp; int err; - if (CONFIG_IS_ENABLED(LWIP_DEBUG_RXTX)) { - printf("net_lwip_tx: %u bytes, udev %s\n", p->len, udev->name); - print_hex_dump("net_lwip_tx: ", 0, 16, 1, p->payload, p->len, - true); - } - - if ((unsigned long)p->payload % PKTALIGN) { + if ((unsigned long)p->payload % PKTALIGN || p->len != p->tot_len) { /* * Some net drivers have strict alignment requirements and may * fail or output invalid data if the packet is not aligned. + * + * A packet may also be stored in multiple chained pbufs. In + * this case, assemble the fragments into one contiguous packet + * buffer before passing it to the Ethernet driver. */ - pp = memalign(PKTALIGN, p->len); + + pp = memalign(PKTALIGN, p->tot_len); if (!pp) - return ERR_ABRT; - memcpy(pp, p->payload, p->len); + return ERR_MEM; + + pp_allocated = true; + + plen = pbuf_copy_partial(p, pp, p->tot_len, 0); + if (plen != p->tot_len) { + free(pp); + return ERR_BUF; + } + } else { + pp = p->payload; + plen = p->len; + } + + if (CONFIG_IS_ENABLED(LWIP_DEBUG_RXTX)) { + printf("net_lwip_tx: %u bytes, udev %s\n", plen, udev->name); + print_hex_dump("net_lwip_tx: ", 0, 16, 1, pp, plen, true); } - err = eth_get_ops(udev)->send(udev, pp ? pp : p->payload, p->len); - free(pp); + err = eth_get_ops(udev)->send(udev, pp, plen); + + if (pp_allocated) + free(pp); + if (err) { debug("send error %d\n", err); return ERR_ABRT; -- cgit v1.3.1 From 2e7e0dbb1594bcb909d369be92a951b17e511a31 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Chaudhary Date: Fri, 10 Jul 2026 20:50:40 +0530 Subject: cmd: lwip: sntp: fix netif leak when ntpserverip is unset sntp_loop() allocates a netif via net_lwip_new_netif() and normally releases it with net_lwip_remove_netif() before returning. The error path taken when no explicit server IP is passed and ntp_server_known() is false returns -1 directly without freeing the netif, leaking the lwIP netif structure (and its associated state) on every failed invocation of the sntp command. Call net_lwip_remove_netif(netif) before returning on this path so it matches the other exits. Signed-off-by: Naveen Kumar Chaudhary Reviewed-by: Jerome Forissier --- cmd/lwip/sntp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/lwip/sntp.c b/cmd/lwip/sntp.c index 5fa400b104a..584151ba7d1 100644 --- a/cmd/lwip/sntp.c +++ b/cmd/lwip/sntp.c @@ -71,6 +71,7 @@ static int sntp_loop(struct udevice *udev, ip_addr_t *srvip) } else { if (!ntp_server_known()) { log_err("error: ntpserverip not set\n"); + net_lwip_remove_netif(netif); return -1; } } -- cgit v1.3.1 From 495368c5538303c3026eb32afd6f7027fbc88a0e Mon Sep 17 00:00:00 2001 From: Naveen Kumar Chaudhary Date: Fri, 10 Jul 2026 20:51:33 +0530 Subject: cmd: lwip: wget: free mbedtls x509 cert context to avoid memory leak _set_cacert() calls mbedtls_x509_crt_init(&crt) followed by mbedtls_x509_crt_parse(), which allocates internal storage (parsed cert fields, chain links, raw buffers) inside the crt object. The function then returns on both the error and success paths without calling mbedtls_x509_crt_free(&crt), so all of that internal state is leaked when the stack-allocated crt goes out of scope. Every invocation of "wget cacert ..." leaks memory. Free the cert object on both return paths. Signed-off-by: Naveen Kumar Chaudhary Reviewed-by: Jerome Forissier --- cmd/lwip/wget.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/lwip/wget.c b/cmd/lwip/wget.c index 4883ad61bce..531e886e986 100644 --- a/cmd/lwip/wget.c +++ b/cmd/lwip/wget.c @@ -67,12 +67,15 @@ static int _set_cacert(const void *addr, size_t sz) if (ret) { if (!wget_info->silent) printf("Could not parse certificates (%d)\n", ret); + mbedtls_x509_crt_free(&crt); free(cacert); cacert = NULL; cacert_size = 0; return CMD_RET_FAILURE; } + mbedtls_x509_crt_free(&crt); + #if CONFIG_IS_ENABLED(WGET_BUILTIN_CACERT) cacert_initialized = true; #endif -- cgit v1.3.1 From f517fdbc0ddcb9bdcb773640f164ddcb924d877c Mon Sep 17 00:00:00 2001 From: Boon Khai Ng Date: Tue, 14 Jul 2026 15:19:27 +0800 Subject: net: dwc_eth_xgmac: Return -ENODEV when phy_connect() fails When multiple Ethernet controllers are enabled in the device tree, but only one controller is actually present in hardware, the non-existent controller still attempts to connect to a PHY. In this case, phy_connect() may return NULL without setting an error code. The current driver only logs the failure but does not propagate an error, causing the initialization flow to continue with an invalid PHY handle. This leads to failures later in the initialization sequence. Fix this by explicitly setting ret = -ENODEV when phy_connect() returns NULL, ensuring the driver exits cleanly on failure. Signed-off-by: Boon Khai Ng --- drivers/net/dwc_eth_xgmac.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/dwc_eth_xgmac.c b/drivers/net/dwc_eth_xgmac.c index 311b57011c3..4e664a88f68 100644 --- a/drivers/net/dwc_eth_xgmac.c +++ b/drivers/net/dwc_eth_xgmac.c @@ -507,6 +507,7 @@ static int xgmac_start(struct udevice *dev) xgmac->config->interface(dev)); if (!xgmac->phy) { pr_err("%s phy_connect() failed\n", dev->name); + ret = -ENODEV; goto err_stop_resets; } -- cgit v1.3.1 From 85d82c52327552da9e9bd103342d970d49ec262c Mon Sep 17 00:00:00 2001 From: Murtaza Munaim Date: Wed, 22 Jul 2026 23:44:29 +0200 Subject: net: nfs: clean up bounds checks in nfs_readlink_reply() Commit d6694018eadd ("net: nfs: fix buffer overflow in nfs_readlink_reply()") added bounds checks against sizeof(nfs_path_buff) before both memcpy() calls. This is a cosmetic cleanup of that fix: - introduce a local new_len for the relative-path branch so the sum pathlen + rlen is computed once and reused for both the bounds check and the NUL terminator, rather than being open-coded twice; - emit a diagnostic when a symlink target is rejected for exceeding the buffer, matching the style of other NFS error paths. No functional change to the accept/reject decision. This same overflow was independently discovered and privately reported to the U-Boot maintainers on 2026-04-03, together with a working proof of concept, ahead of the change that became the fix cited above. This cleanup restores the local-variable form from that original report. Signed-off-by: Murtaza Munaim --- net/nfs-common.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/net/nfs-common.c b/net/nfs-common.c index 72d8fd823e3..637fcfd9bb8 100644 --- a/net/nfs-common.c +++ b/net/nfs-common.c @@ -671,18 +671,24 @@ static int nfs_readlink_reply(uchar *pkt, unsigned int len) if (*((char *)&rpc_pkt.u.reply.data[2 + nfsv3_data_offset]) != '/') { int pathlen; + int new_len; strcat(nfs_path, "/"); pathlen = strlen(nfs_path); - if (pathlen + rlen >= sizeof(nfs_path_buff)) + new_len = pathlen + rlen; + if (new_len >= sizeof(nfs_path_buff)) { + printf("NFS: symlink too long (%d bytes)\n", new_len); return -NFS_RPC_DROP; + } memcpy(nfs_path + pathlen, (uchar *)&rpc_pkt.u.reply.data[2 + nfsv3_data_offset], rlen); - nfs_path[pathlen + rlen] = 0; + nfs_path[new_len] = 0; } else { - if (rlen >= sizeof(nfs_path_buff)) + if (rlen >= sizeof(nfs_path_buff)) { + printf("NFS: symlink too long (%d bytes)\n", rlen); return -NFS_RPC_DROP; + } memcpy(nfs_path, (uchar *)&rpc_pkt.u.reply.data[2 + nfsv3_data_offset], rlen); -- cgit v1.3.1 From 04ca915d5bf39dda5d1bce62d04d2b59d293c5b9 Mon Sep 17 00:00:00 2001 From: Shahriyar Jalayeri Date: Tue, 28 Jul 2026 08:50:07 +0200 Subject: net: fix out-of-bounds write in IP fragment reassembly __net_defragment() reassembles IP fragments into the static buffer pkt_buff[CONFIG_NET_MAXDEFRAG]. The bounds check if (start + len > IP_MAXUDP) return NULL; only covers the fragment data copy. The split-hole and move-hole branches additionally write an 8-byte struct hole via "*newh = *h" at newh = thisfrag + len / 8, which can land up to sizeof(struct hole) bytes past the end of pkt_buff. A single fragment with a non-zero fragment offset and the More-Fragments flag set reaches this path, so a crafted fragment received during netboot overflows the buffer. Reject any fragment whose trailing hole descriptor would fall outside pkt_buff. Signed-off-by: Shahriyar Jalayeri Acked-by: Jerome Forissier --- net/net.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/net.c b/net/net.c index 61c5a6ef6c4..71666eb1113 100644 --- a/net/net.c +++ b/net/net.c @@ -1076,6 +1076,8 @@ static struct ip_udp_hdr *__net_defragment(struct ip_udp_hdr *ip, int *lenp) } else if (h >= thisfrag) { /* overlaps with initial part of the hole: move this hole */ newh = thisfrag + (len / 8); + if ((uchar *)(newh + 1) > pkt_buff + IP_PKTSIZE) + return NULL; /* hole descriptor would overflow pkt_buff */ *newh = *h; h = newh; if (h->next_hole) @@ -1088,6 +1090,8 @@ static struct ip_udp_hdr *__net_defragment(struct ip_udp_hdr *ip, int *lenp) } else { /* fragment sits in the middle: split the hole */ newh = thisfrag + (len / 8); + if ((uchar *)(newh + 1) > pkt_buff + IP_PKTSIZE) + return NULL; /* hole descriptor would overflow pkt_buff */ *newh = *h; h->last_byte = start; h->next_hole = (newh - payload); -- cgit v1.3.1 From 7946774a0feb40a4abc71e749e61071ffce56979 Mon Sep 17 00:00:00 2001 From: Shahriyar Jalayeri Date: Tue, 28 Jul 2026 08:50:08 +0200 Subject: test: net: add regression test for IP reassembly overflow Add a DM test that feeds __net_defragment() a single crafted fragment whose trailing hole descriptor lands just past pkt_buff. Without the preceding fix the 8-byte hole write goes out of bounds; with it the fragment is dropped and no datagram is delivered. Signed-off-by: Shahriyar Jalayeri Acked-by: Jerome Forissier --- test/dm/net_defrag.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/dm/net_defrag.c b/test/dm/net_defrag.c index 3fd40de90cd..7501b252db9 100644 --- a/test/dm/net_defrag.c +++ b/test/dm/net_defrag.c @@ -80,3 +80,39 @@ static int dm_test_net_ip_defrag_dup_last(struct unit_test_state *uts) } DM_TEST(dm_test_net_ip_defrag_dup_last, 0); + +/* + * A fragment placed at the very top of the reassembly buffer takes the + * split-hole branch, which writes an 8-byte "struct hole" at + * pkt_buff + IP_HDR_SIZE + (offset8 + len / 8) * 8. With start + len equal to + * IP_MAXUDP that write reaches the end of pkt_buff and spills past it. pkt_buff + * is a static array, so this is flagged under AddressSanitizer; the fix rejects + * such a fragment instead. The datagram is incomplete, so nothing is delivered + * either way. + */ +static int dm_test_net_ip_defrag_oob(struct unit_test_state *uts) +{ + rxhand_f *saved_handler = net_get_udp_handler(); + uchar frame[FRAME_LEN]; + struct ip_udp_hdr *ip = (struct ip_udp_hdr *)(frame + ETHER_HDR_SIZE); + u16 payload[4] = { 0, 0, 0, 0 }; + /* Offset (8-byte units) so that start + FRAG_LEN == IP_MAXUDP. */ + u16 off8 = (CONFIG_NET_MAXDEFRAG - IP_HDR_SIZE - FRAG_LEN) / 8; + + udp_rx_count = 0; + net_set_udp_handler(defrag_udp_handler); + + build_frag(frame, IP_FLAGS_MFRAG | off8, payload); + /* A distinct id forces a fresh reassembly independent of earlier tests. */ + ip->ip_id = htons(0x7abc); + ip->ip_sum = 0; + ip->ip_sum = compute_ip_checksum(ip, IP_HDR_SIZE); + net_process_received_packet(frame, FRAME_LEN); + + ut_asserteq(0, udp_rx_count); + + net_set_udp_handler(saved_handler); + + return 0; +} +DM_TEST(dm_test_net_ip_defrag_oob, 0); -- cgit v1.3.1 From a1aaacc968ae995b7557a316907c2e2f073b1a17 Mon Sep 17 00:00:00 2001 From: Javen Xu Date: Wed, 22 Jul 2026 15:50:57 +0800 Subject: net: rtl8169: add support for RTL8126A and RTL8127A RTL8126A and RTL8127A share most of the register layouts and quirks with RTL8125 series, but require explicit desc type and CRC-drop setup during init. This patch adds PCI IDs and MAC versions. RTL8126A is 0x66 and RTL8127A is 0x6e. And RTL8127A requires Tx Desc V3 format and an additional RADM FIFO protection configuration according to vendor driver. Signed-off-by: Javen Xu --- drivers/net/rtl8169.c | 103 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 9 deletions(-) diff --git a/drivers/net/rtl8169.c b/drivers/net/rtl8169.c index e203faed26b..0208ff2d435 100644 --- a/drivers/net/rtl8169.c +++ b/drivers/net/rtl8169.c @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -152,8 +153,24 @@ enum RTL8125_registers { IntrMask_8125 = 0x38, IntrStatus_8125 = 0x3C, TxPoll_8125 = 0x90, + RX_DESC_MODE = 0xd8, + MACOCP = 0xb0, + RADMFIFO_PROTECT = 0x0402, + TX_DESC_MODE = 0xeb58, }; +/* MAC OCP indirect access via the MACOCP register */ +#define RTL8125_OCP_WRITE 0x80000000 +#define RTL8125_OCP_ADDR_SHIFT 16 +#define RX_DESC_CRC_DROP BIT(0) +#define RX_DESC_TYPE BIT(1) + +/* TX_DESC_MODE (MAC OCP 0xeb58): TX descriptor format select, bits [1:0] */ +#define TX_DESC_FMT_MASK GENMASK(1, 0) +#define TX_DESC_FMT_STD BIT(0) +#define TX_DESC_FMT_V3 BIT(1) +#define RX_PAUSE_SLOT_ON BIT(11) + enum RTL8169_register_content { /*InterruptStatusBits */ SYSErr = 0x8000, @@ -271,6 +288,8 @@ static struct { {"RTL-8168h/8111h", 0x54, 0xff7e1880,}, {"RTL-8125B", 0x64, 0xff7e1880,}, {"RTL-8125d", 0x6a, 0xff7e5880,}, + {"RTL-8126A", 0x66, 0xff7e5880,}, + {"RTL-8127A", 0x6e, 0xff7e5880,}, }; enum _DescStatusBit { @@ -355,6 +374,8 @@ static const unsigned int rtl8169_rx_config = static struct pci_device_id supported[] = { { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8125) }, + { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8126) }, + { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8127) }, { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8161) }, { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8167) }, { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8168) }, @@ -398,6 +419,33 @@ int mdio_read(int RegAddr) return value; } +/* RTL8125/8126/8127 share the same register layout and quirks */ +static bool rtl_is_8125(struct pci_child_plat *pplat) +{ + return pplat->device == 0x8125 || pplat->device == 0x8126 || + pplat->device == 0x8127; +} + +static void rtl_mac_ocp_write(u16 reg_addr, u16 value) +{ + u32 data32; + + data32 = (u32)(reg_addr / 2) << RTL8125_OCP_ADDR_SHIFT; + data32 += value; + data32 |= RTL8125_OCP_WRITE; + RTL_W32(MACOCP, data32); +} + +static u16 rtl_mac_ocp_read(u16 reg_addr) +{ + u32 data32; + + data32 = (u32)(reg_addr / 2) << RTL8125_OCP_ADDR_SHIFT; + RTL_W32(MACOCP, data32); + + return (u16)RTL_R32(MACOCP); +} + static int rtl8169_init_board(unsigned long dev_iobase, const char *name) { int i; @@ -571,12 +619,15 @@ static int rtl_recv_common(struct udevice *dev, unsigned long dev_iobase, return length; } else { - u32 IntrStatus = IntrStatus_8169; + u32 sts; - if (pplat->device == 0x8125) - IntrStatus = IntrStatus_8125; - ushort sts = RTL_R8(IntrStatus); - RTL_W8(IntrStatus, sts & ~(TxErr | RxErr | SYSErr)); + if (rtl_is_8125(pplat)) { + sts = RTL_R32(IntrStatus_8125); + RTL_W32(IntrStatus_8125, sts & ~(TxErr | RxErr | SYSErr)); + } else { + sts = RTL_R16(IntrStatus_8169); + RTL_W16(IntrStatus_8169, sts & ~(TxErr | RxErr | SYSErr)); + } udelay(100); /* wait */ } tpc->cur_rx = cur_rx; @@ -636,8 +687,8 @@ static int rtl_send_common(struct udevice *dev, unsigned long dev_iobase, ((len > ETH_ZLEN) ? len : ETH_ZLEN)); } rtl_flush_tx_desc(&tpc->TxDescArray[entry]); - if (pplat->device == 0x8125) - RTL_W8(TxPoll_8125, 0x1); /* set polling bit */ + if (rtl_is_8125(pplat)) + RTL_W32(TxPoll_8125, 0x1); /* set polling bit */ else RTL_W8(TxPoll_8169, 0x40); /* set polling bit */ @@ -697,6 +748,8 @@ static void rtl8169_set_rx_mode(void) static void rtl8169_hw_start(struct udevice *dev) { + u8 version = rtl_chip_info[tpc->chipset].version; + u16 tx_desc_mode; u32 i; #ifdef DEBUG_RTL8169 @@ -719,6 +772,36 @@ static void rtl8169_hw_start(struct udevice *dev) RTL_W8(Cfg9346, Cfg9346_Unlock); + /* + * RTL8125D/8126A/8127A require explicit descriptor-type and CRC-drop setup. + * RTL8125B (0x64) is intentionally excluded to avoid regressing working hardware. + */ + switch (version) { + case 0x6a: + case 0x66: + case 0x6e: + /* Reg 0xD8: Disable CRC drop and force legacy 16-byte RX desc */ + RTL_W8(RX_DESC_MODE, RTL_R8(RX_DESC_MODE) & ~(RX_DESC_CRC_DROP | RX_DESC_TYPE)); + + /* RxConfig 0x44: enable RX pause slot (RTL8125B and later) */ + RTL_W32(RxConfig, RTL_R32(RxConfig) | RX_PAUSE_SLOT_ON); + + /* MAC OCP 0xEB58: TX descriptor format setup */ + tx_desc_mode = rtl_mac_ocp_read(TX_DESC_MODE) & ~TX_DESC_FMT_MASK; + + if (version == 0x6e) { + /* RTL8127A specific: Tx desc V3 and RADM FIFO protection */ + rtl_mac_ocp_write(TX_DESC_MODE, tx_desc_mode | TX_DESC_FMT_V3); + RTL_W16(RADMFIFO_PROTECT, 0x2001); + } else { + /* RTL8125D / RTL8126A specific: standard Tx desc */ + rtl_mac_ocp_write(TX_DESC_MODE, tx_desc_mode | TX_DESC_FMT_STD); + } + break; + default: + break; + } + /* RTL-8169sb/8110sb or previous version */ if (tpc->chipset <= 5) RTL_W8(ChipCmd, CmdTxEnb | CmdRxEnb); @@ -860,8 +943,8 @@ static void rtl_halt_common(struct udevice *dev) RTL_W8(ChipCmd, 0x00); /* Disable interrupts by clearing the interrupt mask. */ - if (pplat->device == 0x8125) - RTL_W16(IntrMask_8125, 0x0000); + if (rtl_is_8125(pplat)) + RTL_W32(IntrMask_8125, 0x00000000); else RTL_W16(IntrMask_8169, 0x0000); @@ -1052,6 +1135,8 @@ static int rtl8169_eth_probe(struct udevice *dev) switch (pplat->device) { case 0x8125: + case 0x8126: + case 0x8127: case 0x8161: case 0x8168: region = 2; -- cgit v1.3.1 From 5eb45fb2917c7d46282cd6dd6e05d41b84f67105 Mon Sep 17 00:00:00 2001 From: Luca Jones Date: Tue, 11 Aug 2026 10:25:57 -0700 Subject: net: lwip: dhcp: fix DHCP fine timer interval dhcp_fine_tmr() was called every 10ms, but lwIP expects it to be called every DHCP_FINE_TIMER_MSECS (500ms). Since the fine timer ticks are currently 50 times faster than lwIP expects, the client burns through the bounded exponential backoff sequence for the DHCPREQUEST messages in 2.44 seconds rather than 122 seconds, after which it uses a new xid and returns to sending DHCPDISCOVER messages. If there is enough latency in the server, the client could receive an ACK response within DHCP_TIMEOUT_MS (10 seconds), but reject it because it has already moved on with another xid after 2.44 seconds. We have seen this occur with our boards. When our rack of 16 boards get powered on together, they all request for an address from the network switch's DHCP server in near lock-step and we see that only a few of the boards actually obtain a lease. Fixing the timing to 500ms allowed all of the boards to obtain a lease consistently. Fixes: 98ad145db61a ("net: lwip: add DHCP support and dhcp commmand") Signed-off-by: Luca Jones --- net/lwip/dhcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/lwip/dhcp.c b/net/lwip/dhcp.c index 18dc36ae7ca..a5e2e7d4da0 100644 --- a/net/lwip/dhcp.c +++ b/net/lwip/dhcp.c @@ -25,7 +25,7 @@ static char boot_file_name[DHCP_BOOT_FILE_LEN]; static void call_lwip_dhcp_fine_tmr(void *ctx) { dhcp_fine_tmr(); - sys_timeout(10, call_lwip_dhcp_fine_tmr, NULL); + sys_timeout(DHCP_FINE_TIMER_MSECS, call_lwip_dhcp_fine_tmr, NULL); } static int dhcp_loop(struct udevice *udev) -- cgit v1.3.1 From b752e4417900ae51d4d8fda879a5594db7025698 Mon Sep 17 00:00:00 2001 From: Graeme Smecher Date: Thu, 6 Aug 2026 13:43:42 -0700 Subject: net: srand_mac(): fix -ENODEV crash with CONFIG_DM_RNG The uclass_get_device() return-value check was inverted, resulting in a synchronous abort when -ENODEV was returned. Signed-off-by: Graeme Smecher Reviewed-by: Simon Glass --- net/net_rand.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/net_rand.h b/net/net_rand.h index e21dff8569b..18ff7b68056 100644 --- a/net/net_rand.h +++ b/net/net_rand.h @@ -44,7 +44,7 @@ static inline void srand_mac(void) if (CONFIG_IS_ENABLED(DM_RNG)) { ret = uclass_get_device(UCLASS_RNG, 0, &devp); - if (ret) { + if (!ret) { ret = dm_rng_read(devp, &randv, sizeof(randv)); if (ret < 0) randv = 0; -- cgit v1.3.1