summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2026-08-13 08:58:29 -0600
committerTom Rini <[email protected]>2026-08-13 08:58:29 -0600
commitc2ac5fc4e3bc91f59f059f115939d28fe35a7841 (patch)
treeb522dc46ea7ab571e7f9b550a596b20269189942
parent0b581a9612e46310ee52bf8359165950d38b2a2b (diff)
parentb752e4417900ae51d4d8fda879a5594db7025698 (diff)
Merge tag 'net-20260813' of https://git.u-boot-project.org/u-boot/custodians/u-boot-net
Pull request net-20260813. net: - phy: dp83867: enable extended read / write for driver - phy: fix duplicate eth_phy binding - Drop unnecessary device_set_name - dwc_eth_xgmac: Return -ENODEV when phy_connect() fails - nfs: clean up bounds checks in nfs_readlink_reply() - rtl8169: add support for RTL8126A and RTL8127A - srand_mac(): fix -ENODEV crash with CONFIG_DM_RNG net-legacy: - Fix out-of-bounds write in IP fragment reassembly - test: net: add regression test for IP reassembly overflow net-lwip: - Add tftpsrv command - Handle chained pbufs in transmit path - sntp: fix netif leak when ntpserverip is unset - wget: free mbedtls x509 cert context to avoid memory leak - Fix DHCP fine timer interval
-rw-r--r--cmd/Kconfig14
-rw-r--r--cmd/lwip/Makefile1
-rw-r--r--cmd/lwip/sntp.c1
-rw-r--r--cmd/lwip/tftpsrv.c11
-rw-r--r--cmd/lwip/wget.c3
-rw-r--r--cmd/net.c4
-rw-r--r--configs/qemu_arm64_lwip_defconfig1
-rw-r--r--doc/usage/cmd/tftpsrv.rst73
-rw-r--r--drivers/net/dm9000x.c6
-rw-r--r--drivers/net/dwc_eth_xgmac.c1
-rw-r--r--drivers/net/ftmac100.c6
-rw-r--r--drivers/net/ks8851_mll.c6
-rw-r--r--drivers/net/phy/dp83867.c18
-rw-r--r--drivers/net/phy/phy.c29
-rw-r--r--drivers/net/rtl8169.c103
-rw-r--r--drivers/net/smc911x.c6
-rw-r--r--include/net-lwip.h1
-rw-r--r--lib/lwip/u-boot/lwipopts.h11
-rw-r--r--net/lwip/dhcp.c2
-rw-r--r--net/lwip/net-lwip.c45
-rw-r--r--net/lwip/tftp.c188
-rw-r--r--net/net.c4
-rw-r--r--net/net_rand.h2
-rw-r--r--net/nfs-common.c12
-rw-r--r--test/dm/net_defrag.c36
-rw-r--r--test/py/tests/test_net.py94
26 files changed, 613 insertions, 65 deletions
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/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;
}
}
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 <command.h>
+#include <net.h>
+
+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/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
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/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/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;
}
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/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,
};
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 <asm-generic/gpio.h>
#include <dm/device_compat.h>
#include <dm/of_extra.h>
+#include <dm/uclass-internal.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/err.h>
@@ -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
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 <asm/cache.h>
#include <asm/io.h>
#include <pci.h>
+#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/printk.h>
@@ -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;
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,
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/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)
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;
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 <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 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/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);
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;
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);
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);
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
+
+
[email protected]("cmd_crc32")
[email protected]("cmd_tftpsrv")
+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