summaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
Diffstat (limited to 'net')
-rw-r--r--net/Kconfig265
-rw-r--r--net/Makefile40
-rw-r--r--net/arp.c230
-rw-r--r--net/arp.h30
-rw-r--r--net/bootp.c1163
-rw-r--r--net/bootp.h95
-rw-r--r--net/cdp.c359
-rw-r--r--net/cdp.h22
-rw-r--r--net/dhcpv6.c732
-rw-r--r--net/dhcpv6.h256
-rw-r--r--net/dns.c217
-rw-r--r--net/dns.h35
-rw-r--r--net/dsa-uclass.c533
-rw-r--r--net/eth-uclass.c657
-rw-r--r--net/eth_bootdev.c118
-rw-r--r--net/eth_common.c137
-rw-r--r--net/eth_internal.h19
-rw-r--r--net/fastboot_tcp.c145
-rw-r--r--net/fastboot_udp.c305
-rw-r--r--net/link_local.c348
-rw-r--r--net/link_local.h21
-rw-r--r--net/mdio-mux-uclass.c224
-rw-r--r--net/mdio-uclass.c323
-rw-r--r--net/ndisc.c515
-rw-r--r--net/net.c1780
-rw-r--r--net/net6.c449
-rw-r--r--net/net_rand.h59
-rw-r--r--net/nfs.c984
-rw-r--r--net/nfs.h86
-rw-r--r--net/pcap.c156
-rw-r--r--net/ping.c119
-rw-r--r--net/ping.h31
-rw-r--r--net/ping6.c117
-rw-r--r--net/rarp.c91
-rw-r--r--net/rarp.h28
-rw-r--r--net/sntp.c123
-rw-r--r--net/tcp.c718
-rw-r--r--net/tftp.c989
-rw-r--r--net/udp.c45
-rw-r--r--net/wget.c606
-rw-r--r--net/wol.c95
-rw-r--r--net/wol.h65
42 files changed, 13330 insertions, 0 deletions
diff --git a/net/Kconfig b/net/Kconfig
new file mode 100644
index 00000000000..7cb80b880a9
--- /dev/null
+++ b/net/Kconfig
@@ -0,0 +1,265 @@
+#
+# Network configuration
+#
+
+menuconfig NET
+ bool "Networking support"
+ default y
+ imply NETDEVICES
+
+if NET
+
+config ARP_TIMEOUT
+ int "Milliseconds before trying ARP again"
+ default 5000
+
+config NET_RETRY_COUNT
+ int "Number of timeouts before giving up"
+ default 5
+ help
+ This variable defines the number of retries for network operations
+ like ARP, RARP, TFTP, or BOOTP before giving up the operation.
+
+config PROT_UDP
+ bool "Enable generic udp framework"
+ help
+ Enable a generic udp framework that allows defining a custom
+ handler for udp protocol.
+
+config BOOTDEV_ETH
+ bool "Enable bootdev for ethernet"
+ depends on BOOTSTD
+ default y
+ help
+ Provide a bootdev for ethernet so that is it possible to boot
+ an operationg system over the network, using the PXE (Preboot
+ Execution Environment) protocol.
+
+config BOOTP_SEND_HOSTNAME
+ bool "Send hostname to DNS server"
+ help
+ Some DHCP servers are capable to do a dynamic update of a
+ DNS server. To do this, they need the hostname of the DHCP
+ requester.
+ If CONFIG_BOOTP_SEND_HOSTNAME is defined, the content
+ of the "hostname" environment variable is passed as
+ option 12 to the DHCP server.
+
+config NET_RANDOM_ETHADDR
+ bool "Random ethaddr if unset"
+ help
+ Selecting this will allow the Ethernet interface to function even
+ when the ethaddr variable for that interface is unset. In this case,
+ a random MAC address in the locally administered address space is
+ generated. It will be saved to the appropriate environment variable,
+ too.
+
+config NETCONSOLE
+ bool "NetConsole support"
+ help
+ Support the 'nc' input/output device for networked console.
+ See doc/usage/netconsole.rst for details.
+
+config IP_DEFRAG
+ bool "Support IP datagram reassembly"
+ help
+ Selecting this will enable IP datagram reassembly according
+ to the algorithm in RFC815.
+
+config NET_MAXDEFRAG
+ int "Size of buffer used for IP datagram reassembly"
+ depends on IP_DEFRAG
+ default 16384
+ range 1024 65536
+ help
+ This defines the size of the statically allocated buffer
+ used for reassembly, and thus an upper bound for the size of
+ IP datagrams that can be received.
+
+config SYS_FAULT_ECHO_LINK_DOWN
+ bool "Echo the inverted Ethernet link state to the fault LED"
+ help
+ Echo the inverted Ethernet link state to the fault LED. Note, if
+ this option is active, then CONFIG_SYS_FAULT_MII_ADDR also needs to
+ be configured.
+
+config TFTP_BLOCKSIZE
+ int "TFTP block size"
+ default 1468
+ help
+ Default TFTP block size.
+ The MTU is typically 1500 for ethernet, so a TFTP block of
+ 1468 (MTU minus eth.hdrs) provides a good throughput with
+ almost-MTU block sizes.
+ You can also activate CONFIG_IP_DEFRAG to set a larger block.
+
+config TFTP_PORT
+ bool "Set TFTP UDP source/destination ports via the environment"
+ help
+ If this is defined, the environment variable tftpsrcp is used to
+ supply the TFTP UDP source port value. If tftpsrcp isn't defined,
+ the normal pseudo-random port number generator is used.
+
+ Also, the environment variable tftpdstp is used to supply the TFTP
+ UDP destination port value. If tftpdstp isn't defined, the normal
+ port 69 is used.
+
+ The purpose for tftpsrcp is to allow a TFTP server to blindly start
+ the TFTP transfer using the pre-configured target IP address and UDP
+ port. This has the effect of "punching through" the (Windows XP)
+ firewall, allowing the remainder of the TFTP transfer to proceed
+ normally. A better solution is to properly configure the firewall,
+ but sometimes that is not allowed.
+
+config TFTP_WINDOWSIZE
+ int "TFTP window size"
+ default 1
+ help
+ Default TFTP window size.
+ RFC7440 defines an optional window size of transmits,
+ before an ack response is required.
+ The default TFTP implementation implies a window size of 1.
+
+config TFTP_TSIZE
+ bool "Track TFTP transfers based on file size option"
+ depends on CMD_TFTPBOOT
+ default y if (ARCH_OMAP2PLUS || ARCH_K3)
+ help
+ By default, TFTP progress bar is increased for each received UDP
+ frame, which can lead into long time being spent for sending
+ data over the UART. Enabling this option, TFTP queries the file
+ size from server, and if supported, limits the progress bar to
+ 50 characters total which fits on single line.
+
+config SERVERIP_FROM_PROXYDHCP
+ bool "Get serverip value from Proxy DHCP response"
+ help
+ Allows bootfile config to be fetched from Proxy DHCP server
+ while IP is obtained from main DHCP server.
+
+config SERVERIP_FROM_PROXYDHCP_DELAY_MS
+ int "# of additional milliseconds to wait for ProxyDHCP response"
+ default 100
+ help
+ Amount of additional time to wait for ProxyDHCP response after
+ receiving response from main DHCP server. Has no effect if
+ SERVERIP_FROM_PROXYDHCP is false.
+
+config KEEP_SERVERADDR
+ bool "Write the server's MAC address to 'serveraddr'"
+ default y if SANDBOX
+ help
+ Keeps the server's MAC address, in the env 'serveraddr'
+ for passing to bootargs (like Linux's netconsole option). If this is
+ enabled, when an ARP reply is received, the server's IP address is
+ written there.
+
+config UDP_CHECKSUM
+ bool "Check the UDP checksum"
+ default y if SANDBOX
+ help
+ Enable this to verify the checksum on UDP packets. If the checksum
+ is wrong then the packet is discarded and an error is shown, like
+ "UDP wrong checksum 29374a23 30ff3826"
+
+config BOOTP_SERVERIP
+ bool "Use the 'serverip' env var for tftp, not bootp"
+ help
+ Enable this if the TFTP server will be the 'serverip' environment
+ variable, not the BOOTP server. This affects the operation of both
+ bootp and tftp.
+
+config BOOTP_MAX_ROOT_PATH_LEN
+ int "Option 17 root path length"
+ default 64
+ help
+ Select maximal length of option 17 root path.
+
+config USE_GATEWAYIP
+ bool "Set a default 'gateway' value in the environment"
+ help
+ Defines a default value for the IP address of the default router
+ where packets to other networks are sent to. (Environment variable
+ "gatewayip")
+
+config GATEWAYIP
+ string "Value of the default 'gateway' value in the environment"
+ depends on USE_GATEWAYIP
+
+config USE_IPADDR
+ bool "Set a default 'ipaddr' value in the environment"
+ help
+ Define a default value for the IP address to use for the default
+ Ethernet interface, in case this is not determined through e.g.
+ bootp. (Environment variable "ipaddr")
+
+config IPADDR
+ string "Value of the default 'ipaddr' value in the environment"
+ depends on USE_IPADDR
+
+config USE_NETMASK
+ bool "Set a default 'netmask' value in the environment"
+ help
+ Defines a default value for the subnet mask (or routing prefix) which
+ is used to determine if an IP address belongs to the local subnet or
+ needs to be forwarded through a router. (Environment variable "netmask")
+
+config NETMASK
+ string "Value of the default 'netmask' value in the environment"
+ depends on USE_NETMASK
+
+config USE_ROOTPATH
+ bool "Set a default 'rootpath' value in the environment"
+
+config ROOTPATH
+ string "Value of the default 'rootpath' value in the environment"
+ depends on USE_ROOTPATH
+ default "/opt/nfsroot"
+
+config USE_SERVERIP
+ bool "Set a default 'serverip' value in the environment"
+ help
+ Defines a default value for the IP address of a TFTP server to
+ contact when using the "tftboot" command. (Environment variable
+ "serverip")
+
+config SERVERIP
+ string "Value of the default 'serverip' value in the environment"
+ depends on USE_SERVERIP
+
+config PROT_TCP
+ bool "TCP stack"
+ help
+ Enable a generic tcp framework that allows defining a custom
+ handler for tcp protocol.
+
+config PROT_TCP_SACK
+ bool "TCP SACK support"
+ depends on PROT_TCP
+ help
+ TCP protocol with SACK. SACK means selective acknowledgements.
+ By turning this option on TCP will learn what segments are already
+ received. So that it improves TCP's retransmission efficiency.
+ This option should be turn on if you want to achieve the fastest
+ file transfer possible.
+
+config IPV6
+ bool "IPv6 support"
+ help
+ Enable IPv6 support. It includes Neighbour Discovery protocol, ICMPv6
+ and auxiliary stuff to make it work. Since it is enabled u-boot
+ network subsystem can get and handle incoming packets and send packets
+ through IPv6 network. It allows to use environment variables such as
+ ip6addr, serverip6. If a u-boot command is capable to parse an IPv6
+ address and find it, it will force using IPv6 in the network stack.
+
+endif # if NET
+
+config SYS_RX_ETH_BUFFER
+ int "Number of receive packet buffers"
+ default 4
+ help
+ Defines the number of Ethernet receive buffers. On some Ethernet
+ controllers it is recommended to set this value to 8 or even higher,
+ since all buffers can be full shortly after enabling the interface on
+ high Ethernet traffic.
diff --git a/net/Makefile b/net/Makefile
new file mode 100644
index 00000000000..a7075c36a04
--- /dev/null
+++ b/net/Makefile
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# (C) Copyright 2000-2006
+# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+
+#ccflags-y += -DDEBUG
+
+obj-$(CONFIG_NET) += arp.o
+obj-$(CONFIG_CMD_BOOTP) += bootp.o
+obj-$(CONFIG_CMD_CDP) += cdp.o
+obj-$(CONFIG_CMD_DNS) += dns.o
+obj-$(CONFIG_DM_DSA) += dsa-uclass.o
+obj-$(CONFIG_$(XPL_)DM_ETH) += eth-uclass.o
+obj-$(CONFIG_$(PHASE_)BOOTDEV_ETH) += eth_bootdev.o
+obj-$(CONFIG_DM_MDIO) += mdio-uclass.o
+obj-$(CONFIG_DM_MDIO_MUX) += mdio-mux-uclass.o
+obj-$(CONFIG_$(XPL_)DM_ETH) += eth_common.o
+obj-$(CONFIG_CMD_LINK_LOCAL) += link_local.o
+obj-$(CONFIG_IPV6) += ndisc.o
+obj-$(CONFIG_$(XPL_)DM_ETH) += net.o
+obj-$(CONFIG_IPV6) += net6.o
+obj-$(CONFIG_CMD_NFS) += nfs.o
+obj-$(CONFIG_CMD_PING) += ping.o
+obj-$(CONFIG_CMD_PING6) += ping6.o
+obj-$(CONFIG_CMD_DHCP6) += dhcpv6.o
+obj-$(CONFIG_CMD_PCAP) += pcap.o
+obj-$(CONFIG_CMD_RARP) += rarp.o
+obj-$(CONFIG_CMD_SNTP) += sntp.o
+obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o
+obj-$(CONFIG_$(PHASE_)UDP_FUNCTION_FASTBOOT) += fastboot_udp.o
+obj-$(CONFIG_$(PHASE_)TCP_FUNCTION_FASTBOOT) += fastboot_tcp.o
+obj-$(CONFIG_CMD_WOL) += wol.o
+obj-$(CONFIG_PROT_UDP) += udp.o
+obj-$(CONFIG_PROT_TCP) += tcp.o
+obj-$(CONFIG_CMD_WGET) += wget.o
+
+# Disable this warning as it is triggered by:
+# sprintf(buf, index ? "foo%d" : "foo", index)
+# and this is intentional usage.
+CFLAGS_eth_common.o += -Wno-format-extra-args
diff --git a/net/arp.c b/net/arp.c
new file mode 100644
index 00000000000..bc1e25f941f
--- /dev/null
+++ b/net/arp.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copied from Linux Monitor (LiMon) - Networking.
+ *
+ * Copyright 1994 - 2000 Neil Russell.
+ * (See License)
+ * Copyright 2000 Roland Borde
+ * Copyright 2000 Paolo Scaffardi
+ * Copyright 2000-2002 Wolfgang Denk, wd@denx.de
+ */
+
+#include <env.h>
+#include <log.h>
+#include <net.h>
+#include <vsprintf.h>
+#include <linux/delay.h>
+
+#include "arp.h"
+
+struct in_addr net_arp_wait_packet_ip;
+static struct in_addr net_arp_wait_reply_ip;
+/* MAC address of waiting packet's destination */
+uchar *arp_wait_packet_ethaddr;
+int arp_wait_tx_packet_size;
+ulong arp_wait_timer_start;
+int arp_wait_try;
+uchar *arp_tx_packet; /* THE ARP transmit packet */
+static uchar arp_tx_packet_buf[PKTSIZE_ALIGN + PKTALIGN];
+
+void arp_init(void)
+{
+ /* XXX problem with bss workaround */
+ arp_wait_packet_ethaddr = NULL;
+ net_arp_wait_packet_ip.s_addr = 0;
+ net_arp_wait_reply_ip.s_addr = 0;
+ arp_wait_tx_packet_size = 0;
+ arp_tx_packet = &arp_tx_packet_buf[0] + (PKTALIGN - 1);
+ arp_tx_packet -= (ulong)arp_tx_packet % PKTALIGN;
+}
+
+void arp_raw_request(struct in_addr source_ip, const uchar *target_ethaddr,
+ struct in_addr target_ip)
+{
+ uchar *pkt;
+ struct arp_hdr *arp;
+ int eth_hdr_size;
+
+ debug_cond(DEBUG_DEV_PKT, "ARP broadcast %d\n", arp_wait_try);
+
+ pkt = arp_tx_packet;
+
+ eth_hdr_size = net_set_ether(pkt, net_bcast_ethaddr, PROT_ARP);
+ pkt += eth_hdr_size;
+
+ arp = (struct arp_hdr *)pkt;
+
+ arp->ar_hrd = htons(ARP_ETHER);
+ arp->ar_pro = htons(PROT_IP);
+ arp->ar_hln = ARP_HLEN;
+ arp->ar_pln = ARP_PLEN;
+ arp->ar_op = htons(ARPOP_REQUEST);
+
+ memcpy(&arp->ar_sha, net_ethaddr, ARP_HLEN); /* source ET addr */
+ net_write_ip(&arp->ar_spa, source_ip); /* source IP addr */
+ memcpy(&arp->ar_tha, target_ethaddr, ARP_HLEN); /* target ET addr */
+ net_write_ip(&arp->ar_tpa, target_ip); /* target IP addr */
+
+ net_send_packet(arp_tx_packet, eth_hdr_size + ARP_HDR_SIZE);
+}
+
+void arp_request(void)
+{
+ if ((net_arp_wait_packet_ip.s_addr & net_netmask.s_addr) !=
+ (net_ip.s_addr & net_netmask.s_addr)) {
+ if (net_gateway.s_addr == 0) {
+ puts("## Warning: gatewayip needed but not set\n");
+ net_arp_wait_reply_ip = net_arp_wait_packet_ip;
+ } else {
+ net_arp_wait_reply_ip = net_gateway;
+ }
+ } else {
+ net_arp_wait_reply_ip = net_arp_wait_packet_ip;
+ }
+
+ arp_raw_request(net_ip, net_null_ethaddr, net_arp_wait_reply_ip);
+}
+
+int arp_timeout_check(void)
+{
+ ulong t;
+
+ if (!arp_is_waiting())
+ return 0;
+
+ t = get_timer(0);
+
+ /* check for arp timeout */
+ if ((t - arp_wait_timer_start) > CONFIG_ARP_TIMEOUT) {
+ arp_wait_try++;
+
+ if (arp_wait_try >= CONFIG_NET_RETRY_COUNT) {
+ puts("\nARP Retry count exceeded; starting again\n");
+ arp_wait_try = 0;
+ net_set_state(NETLOOP_FAIL);
+ } else {
+ arp_wait_timer_start = t;
+ arp_request();
+ }
+ }
+ return 1;
+}
+
+void arp_receive(struct ethernet_hdr *et, struct ip_udp_hdr *ip, int len)
+{
+ struct arp_hdr *arp;
+ struct in_addr reply_ip_addr;
+ int eth_hdr_size;
+ uchar *tx_packet;
+
+ /*
+ * We have to deal with two types of ARP packets:
+ * - REQUEST packets will be answered by sending our
+ * IP address - if we know it.
+ * - REPLY packates are expected only after we asked
+ * for the TFTP server's or the gateway's ethernet
+ * address; so if we receive such a packet, we set
+ * the server ethernet address
+ */
+ debug_cond(DEBUG_NET_PKT, "Got ARP\n");
+
+ arp = (struct arp_hdr *)ip;
+ if (len < ARP_HDR_SIZE) {
+ printf("bad length %d < %d\n", len, ARP_HDR_SIZE);
+ return;
+ }
+ if (ntohs(arp->ar_hrd) != ARP_ETHER)
+ return;
+ if (ntohs(arp->ar_pro) != PROT_IP)
+ return;
+ if (arp->ar_hln != ARP_HLEN)
+ return;
+ if (arp->ar_pln != ARP_PLEN)
+ return;
+
+ if (net_ip.s_addr == 0)
+ return;
+
+ if (net_read_ip(&arp->ar_tpa).s_addr != net_ip.s_addr)
+ return;
+
+ switch (ntohs(arp->ar_op)) {
+ case ARPOP_REQUEST:
+ /* reply with our IP address */
+ debug_cond(DEBUG_DEV_PKT, "Got ARP REQUEST, return our IP\n");
+ eth_hdr_size = net_update_ether(et, et->et_src, PROT_ARP);
+ arp->ar_op = htons(ARPOP_REPLY);
+ memcpy(&arp->ar_tha, &arp->ar_sha, ARP_HLEN);
+ net_copy_ip(&arp->ar_tpa, &arp->ar_spa);
+ memcpy(&arp->ar_sha, net_ethaddr, ARP_HLEN);
+ net_copy_ip(&arp->ar_spa, &net_ip);
+
+#ifdef CONFIG_CMD_LINK_LOCAL
+ /*
+ * Work-around for brain-damaged Cisco equipment with
+ * arp-proxy enabled.
+ *
+ * If the requesting IP is not on our subnet, wait 5ms to
+ * reply to ARP request so that our reply will overwrite
+ * the arp-proxy's instead of the other way around.
+ */
+ if ((net_read_ip(&arp->ar_tpa).s_addr & net_netmask.s_addr) !=
+ (net_read_ip(&arp->ar_spa).s_addr & net_netmask.s_addr))
+ udelay(5000);
+#endif
+ tx_packet = net_get_async_tx_pkt_buf();
+ memcpy(tx_packet, et, eth_hdr_size + ARP_HDR_SIZE);
+ net_send_packet(tx_packet, eth_hdr_size + ARP_HDR_SIZE);
+ return;
+
+ case ARPOP_REPLY: /* arp reply */
+ /* are we waiting for a reply? */
+ if (!arp_is_waiting())
+ break;
+
+ if (IS_ENABLED(CONFIG_KEEP_SERVERADDR) &&
+ net_server_ip.s_addr == net_arp_wait_packet_ip.s_addr) {
+ char buf[20];
+ sprintf(buf, "%pM", &arp->ar_sha);
+ env_set("serveraddr", buf);
+ }
+
+ reply_ip_addr = net_read_ip(&arp->ar_spa);
+
+ /* matched waiting packet's address */
+ if (reply_ip_addr.s_addr == net_arp_wait_reply_ip.s_addr) {
+ debug_cond(DEBUG_DEV_PKT,
+ "Got ARP REPLY, set eth addr (%pM)\n",
+ arp->ar_data);
+
+ /* save address for later use */
+ if (arp_wait_packet_ethaddr != NULL)
+ memcpy(arp_wait_packet_ethaddr,
+ &arp->ar_sha, ARP_HLEN);
+
+ net_get_arp_handler()((uchar *)arp, 0, reply_ip_addr,
+ 0, len);
+
+ /* set the mac address in the waiting packet's header
+ and transmit it */
+ memcpy(((struct ethernet_hdr *)net_tx_packet)->et_dest,
+ &arp->ar_sha, ARP_HLEN);
+ net_send_packet(net_tx_packet, arp_wait_tx_packet_size);
+
+ /* no arp request pending now */
+ net_arp_wait_packet_ip.s_addr = 0;
+ arp_wait_tx_packet_size = 0;
+ arp_wait_packet_ethaddr = NULL;
+ }
+ return;
+ default:
+ debug("Unexpected ARP opcode 0x%x\n",
+ ntohs(arp->ar_op));
+ return;
+ }
+}
+
+bool arp_is_waiting(void)
+{
+ return !!net_arp_wait_packet_ip.s_addr;
+}
diff --git a/net/arp.h b/net/arp.h
new file mode 100644
index 00000000000..c50885fb9a5
--- /dev/null
+++ b/net/arp.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copied from Linux Monitor (LiMon) - Networking.
+ *
+ * Copyright 1994 - 2000 Neil Russell.
+ * (See License)
+ * Copyright 2000 Roland Borde
+ * Copyright 2000 Paolo Scaffardi
+ * Copyright 2000-2002 Wolfgang Denk, wd@denx.de
+ */
+
+#ifndef __ARP_H__
+#define __ARP_H__
+
+extern struct in_addr net_arp_wait_packet_ip;
+/* MAC address of waiting packet's destination */
+extern uchar *arp_wait_packet_ethaddr;
+extern int arp_wait_tx_packet_size;
+extern ulong arp_wait_timer_start;
+extern int arp_wait_try;
+extern uchar *arp_tx_packet;
+
+void arp_init(void);
+void arp_request(void);
+void arp_raw_request(struct in_addr source_ip, const uchar *targetEther,
+ struct in_addr target_ip);
+int arp_timeout_check(void);
+void arp_receive(struct ethernet_hdr *et, struct ip_udp_hdr *ip, int len);
+
+#endif /* __ARP_H__ */
diff --git a/net/bootp.c b/net/bootp.c
new file mode 100644
index 00000000000..afd5b48094a
--- /dev/null
+++ b/net/bootp.c
@@ -0,0 +1,1163 @@
+/*
+ * Based on LiMon - BOOTP.
+ *
+ * Copyright 1994, 1995, 2000 Neil Russell.
+ * (See License)
+ * Copyright 2000 Roland Borde
+ * Copyright 2000 Paolo Scaffardi
+ * Copyright 2000-2004 Wolfgang Denk, wd@denx.de
+ */
+
+#include <bootstage.h>
+#include <command.h>
+#include <env.h>
+#include <efi_loader.h>
+#include <log.h>
+#include <net.h>
+#include <rand.h>
+#include <u-boot/uuid.h>
+#include <linux/delay.h>
+#include <net/tftp.h>
+#include "bootp.h"
+#ifdef CONFIG_LED_STATUS
+#include <status_led.h>
+#endif
+#ifdef CONFIG_BOOTP_RANDOM_DELAY
+#include "net_rand.h"
+#endif
+#include <malloc.h>
+
+#define BOOTP_VENDOR_MAGIC 0x63825363 /* RFC1048 Magic Cookie */
+
+/*
+ * The timeout for the initial BOOTP/DHCP request used to be described by a
+ * counter of fixed-length timeout periods. CONFIG_NET_RETRY_COUNT represents
+ * that counter
+ *
+ * Now that the timeout periods are variable (exponential backoff and retry)
+ * we convert the timeout count to the absolute time it would have take to
+ * execute that many retries, and keep sending retry packets until that time
+ * is reached.
+ */
+#define TIMEOUT_MS ((3 + (CONFIG_NET_RETRY_COUNT * 5)) * 1000)
+
+#ifndef CFG_DHCP_MIN_EXT_LEN /* minimal length of extension list */
+#define CFG_DHCP_MIN_EXT_LEN 64
+#endif
+
+#ifndef CFG_BOOTP_ID_CACHE_SIZE
+#define CFG_BOOTP_ID_CACHE_SIZE 4
+#endif
+
+u32 bootp_ids[CFG_BOOTP_ID_CACHE_SIZE];
+unsigned int bootp_num_ids;
+int bootp_try;
+ulong bootp_start;
+ulong bootp_timeout;
+char net_nis_domain[32] = {0,}; /* Our NIS domain */
+char net_hostname[32] = {0,}; /* Our hostname */
+char net_root_path[CONFIG_BOOTP_MAX_ROOT_PATH_LEN] = {0,}; /* Our bootpath */
+
+static ulong time_taken_max;
+
+#if defined(CONFIG_CMD_DHCP)
+static dhcp_state_t dhcp_state = INIT;
+static u32 dhcp_leasetime;
+static struct in_addr dhcp_server_ip;
+static u8 dhcp_option_overload;
+#define OVERLOAD_FILE 1
+#define OVERLOAD_SNAME 2
+static void dhcp_handler(uchar *pkt, unsigned dest, struct in_addr sip,
+ unsigned src, unsigned len);
+
+/* For Debug */
+#if 0
+static char *dhcpmsg2str(int type)
+{
+ switch (type) {
+ case 1: return "DHCPDISCOVER"; break;
+ case 2: return "DHCPOFFER"; break;
+ case 3: return "DHCPREQUEST"; break;
+ case 4: return "DHCPDECLINE"; break;
+ case 5: return "DHCPACK"; break;
+ case 6: return "DHCPNACK"; break;
+ case 7: return "DHCPRELEASE"; break;
+ default: return "UNKNOWN/INVALID MSG TYPE"; break;
+ }
+}
+#endif
+#endif
+
+static void bootp_add_id(ulong id)
+{
+ if (bootp_num_ids >= ARRAY_SIZE(bootp_ids)) {
+ size_t size = sizeof(bootp_ids) - sizeof(id);
+
+ memmove(bootp_ids, &bootp_ids[1], size);
+ bootp_ids[bootp_num_ids - 1] = id;
+ } else {
+ bootp_ids[bootp_num_ids] = id;
+ bootp_num_ids++;
+ }
+}
+
+static bool bootp_match_id(ulong id)
+{
+ unsigned int i;
+
+ for (i = 0; i < bootp_num_ids; i++)
+ if (bootp_ids[i] == id)
+ return true;
+
+ return false;
+}
+
+static int check_reply_packet(uchar *pkt, unsigned dest, unsigned src,
+ unsigned len)
+{
+ struct bootp_hdr *bp = (struct bootp_hdr *)pkt;
+ int retval = 0;
+
+ if (dest != PORT_BOOTPC || src != PORT_BOOTPS)
+ retval = -1;
+ else if (len < sizeof(struct bootp_hdr) - OPT_FIELD_SIZE)
+ retval = -2;
+ else if (bp->bp_op != OP_BOOTREPLY)
+ retval = -3;
+ else if (bp->bp_htype != HWT_ETHER)
+ retval = -4;
+ else if (bp->bp_hlen != HWL_ETHER)
+ retval = -5;
+ else if (!bootp_match_id(net_read_u32(&bp->bp_id)))
+ retval = -6;
+ else if (memcmp(bp->bp_chaddr, net_ethaddr, HWL_ETHER) != 0)
+ retval = -7;
+
+ debug("Filtering pkt = %d\n", retval);
+
+ return retval;
+}
+
+static void store_bootp_params(struct bootp_hdr *bp)
+{
+ struct in_addr tmp_ip;
+ bool overwrite_serverip = true;
+
+ if (IS_ENABLED(CONFIG_BOOTP_SERVERIP))
+ return;
+
+#if defined(CONFIG_BOOTP_PREFER_SERVERIP)
+ overwrite_serverip = false;
+#endif
+
+ net_copy_ip(&tmp_ip, &bp->bp_siaddr);
+ if (tmp_ip.s_addr != 0 && (overwrite_serverip || !net_server_ip.s_addr))
+ net_copy_ip(&net_server_ip, &bp->bp_siaddr);
+ memcpy(net_server_ethaddr,
+ ((struct ethernet_hdr *)net_rx_packet)->et_src, 6);
+ if (
+#if defined(CONFIG_CMD_DHCP)
+ !(dhcp_option_overload & OVERLOAD_FILE) &&
+#endif
+ (strlen(bp->bp_file) > 0) &&
+ !net_boot_file_name_explicit) {
+ copy_filename(net_boot_file_name, bp->bp_file,
+ sizeof(net_boot_file_name));
+ }
+
+ debug("net_boot_file_name: %s\n", net_boot_file_name);
+
+ /* Propagate to environment:
+ * don't delete exising entry when BOOTP / DHCP reply does
+ * not contain a new value
+ */
+ if (*net_boot_file_name)
+ env_set("bootfile", net_boot_file_name);
+}
+
+/*
+ * Copy parameters of interest from BOOTP_REPLY/DHCP_OFFER packet
+ */
+static void store_net_params(struct bootp_hdr *bp)
+{
+#if !defined(CONFIG_SERVERIP_FROM_PROXYDHCP)
+ store_bootp_params(bp);
+#endif
+ net_copy_ip(&net_ip, &bp->bp_yiaddr);
+}
+
+static int truncate_sz(const char *name, int maxlen, int curlen)
+{
+ if (curlen >= maxlen) {
+ printf("*** WARNING: %s is too long (%d - max: %d)"
+ " - truncated\n", name, curlen, maxlen);
+ curlen = maxlen - 1;
+ }
+ return curlen;
+}
+
+#if !defined(CONFIG_CMD_DHCP)
+
+static void bootp_process_vendor_field(u8 *ext)
+{
+ int size = *(ext + 1);
+
+ debug("[BOOTP] Processing extension %d... (%d bytes)\n", *ext,
+ *(ext + 1));
+
+ net_boot_file_expected_size_in_blocks = 0;
+
+ switch (*ext) {
+ /* Fixed length fields */
+ case 1: /* Subnet mask */
+ if (net_netmask.s_addr == 0)
+ net_copy_ip(&net_netmask, (struct in_addr *)(ext + 2));
+ break;
+ case 2: /* Time offset - Not yet supported */
+ break;
+ /* Variable length fields */
+ case 3: /* Gateways list */
+ if (net_gateway.s_addr == 0)
+ net_copy_ip(&net_gateway, (struct in_addr *)(ext + 2));
+ break;
+ case 4: /* Time server - Not yet supported */
+ break;
+ case 5: /* IEN-116 name server - Not yet supported */
+ break;
+ case 6:
+ if (net_dns_server.s_addr == 0)
+ net_copy_ip(&net_dns_server,
+ (struct in_addr *)(ext + 2));
+#if defined(CONFIG_BOOTP_DNS2)
+ if ((net_dns_server2.s_addr == 0) && (size > 4))
+ net_copy_ip(&net_dns_server2,
+ (struct in_addr *)(ext + 2 + 4));
+#endif
+ break;
+ case 7: /* Log server - Not yet supported */
+ break;
+ case 8: /* Cookie/Quote server - Not yet supported */
+ break;
+ case 9: /* LPR server - Not yet supported */
+ break;
+ case 10: /* Impress server - Not yet supported */
+ break;
+ case 11: /* RPL server - Not yet supported */
+ break;
+ case 12: /* Host name */
+ if (net_hostname[0] == 0) {
+ size = truncate_sz("Host Name",
+ sizeof(net_hostname), size);
+ memcpy(&net_hostname, ext + 2, size);
+ net_hostname[size] = 0;
+ }
+ break;
+ case 13: /* Boot file size */
+ if (size == 2)
+ net_boot_file_expected_size_in_blocks =
+ ntohs(*(ushort *)(ext + 2));
+ else if (size == 4)
+ net_boot_file_expected_size_in_blocks =
+ ntohl(*(ulong *)(ext + 2));
+ break;
+ case 14: /* Merit dump file - Not yet supported */
+ break;
+ case 15: /* Domain name - Not yet supported */
+ break;
+ case 16: /* Swap server - Not yet supported */
+ break;
+ case 17: /* Root path */
+ if (net_root_path[0] == 0) {
+ size = truncate_sz("Root Path",
+ sizeof(net_root_path), size);
+ memcpy(&net_root_path, ext + 2, size);
+ net_root_path[size] = 0;
+ }
+ break;
+ case 18: /* Extension path - Not yet supported */
+ /*
+ * This can be used to send the information of the
+ * vendor area in another file that the client can
+ * access via TFTP.
+ */
+ break;
+ /* IP host layer fields */
+ case 40: /* NIS Domain name */
+ if (net_nis_domain[0] == 0) {
+ size = truncate_sz("NIS Domain Name",
+ sizeof(net_nis_domain), size);
+ memcpy(&net_nis_domain, ext + 2, size);
+ net_nis_domain[size] = 0;
+ }
+ break;
+#if defined(CONFIG_CMD_SNTP) && defined(CONFIG_BOOTP_NTPSERVER)
+ case 42: /* NTP server IP */
+ net_copy_ip(&net_ntp_server, (struct in_addr *)(ext + 2));
+ break;
+#endif
+ /* Application layer fields */
+ case 43: /* Vendor specific info - Not yet supported */
+ /*
+ * Binary information to exchange specific
+ * product information.
+ */
+ break;
+ /* Reserved (custom) fields (128..254) */
+ }
+}
+
+static void bootp_process_vendor(u8 *ext, int size)
+{
+ u8 *end = ext + size;
+
+ debug("[BOOTP] Checking extension (%d bytes)...\n", size);
+
+ while ((ext < end) && (*ext != 0xff)) {
+ if (*ext == 0) {
+ ext++;
+ } else {
+ u8 *opt = ext;
+
+ ext += ext[1] + 2;
+ if (ext <= end)
+ bootp_process_vendor_field(opt);
+ }
+ }
+
+ debug("[BOOTP] Received fields:\n");
+ if (net_netmask.s_addr)
+ debug("net_netmask : %pI4\n", &net_netmask);
+
+ if (net_gateway.s_addr)
+ debug("net_gateway : %pI4", &net_gateway);
+
+ if (net_boot_file_expected_size_in_blocks)
+ debug("net_boot_file_expected_size_in_blocks : %d\n",
+ net_boot_file_expected_size_in_blocks);
+
+ if (net_hostname[0])
+ debug("net_hostname : %s\n", net_hostname);
+
+ if (net_root_path[0])
+ debug("net_root_path : %s\n", net_root_path);
+
+ if (net_nis_domain[0])
+ debug("net_nis_domain : %s\n", net_nis_domain);
+
+#if defined(CONFIG_CMD_SNTP) && defined(CONFIG_BOOTP_NTPSERVER)
+ if (net_ntp_server.s_addr)
+ debug("net_ntp_server : %pI4\n", &net_ntp_server);
+#endif
+}
+
+/*
+ * Handle a BOOTP received packet.
+ */
+static void bootp_handler(uchar *pkt, unsigned dest, struct in_addr sip,
+ unsigned src, unsigned len)
+{
+ struct bootp_hdr *bp;
+
+ debug("got BOOTP packet (src=%d, dst=%d, len=%d want_len=%zu)\n",
+ src, dest, len, sizeof(struct bootp_hdr));
+
+ bp = (struct bootp_hdr *)pkt;
+
+ /* Filter out pkts we don't want */
+ if (check_reply_packet(pkt, dest, src, len))
+ return;
+
+ /*
+ * Got a good BOOTP reply. Copy the data into our variables.
+ */
+#if defined(CONFIG_LED_STATUS) && defined(CONFIG_LED_STATUS_BOOT_ENABLE)
+ status_led_set(CONFIG_LED_STATUS_BOOT, CONFIG_LED_STATUS_OFF);
+#endif
+
+ store_net_params(bp); /* Store net parameters from reply */
+
+ /* Retrieve extended information (we must parse the vendor area) */
+ if (net_read_u32((u32 *)&bp->bp_vend[0]) == htonl(BOOTP_VENDOR_MAGIC))
+ bootp_process_vendor((uchar *)&bp->bp_vend[4], len);
+
+ net_set_timeout_handler(0, (thand_f *)0);
+ bootstage_mark_name(BOOTSTAGE_ID_BOOTP_STOP, "bootp_stop");
+
+ debug("Got good BOOTP\n");
+
+ net_auto_load();
+}
+#endif
+
+/*
+ * Timeout on BOOTP/DHCP request.
+ */
+static void bootp_timeout_handler(void)
+{
+ ulong time_taken = get_timer(bootp_start);
+
+ if (time_taken >= time_taken_max) {
+#ifdef CONFIG_BOOTP_MAY_FAIL
+ char *ethrotate;
+
+ ethrotate = env_get("ethrotate");
+ if ((ethrotate && strcmp(ethrotate, "no") == 0) ||
+ net_restart_wrap) {
+ puts("\nRetry time exceeded\n");
+ net_set_state(NETLOOP_FAIL);
+ } else
+#endif
+ {
+ puts("\nRetry time exceeded; starting again\n");
+ net_start_again();
+ }
+ } else {
+ bootp_timeout *= 2;
+ if (bootp_timeout > 2000)
+ bootp_timeout = 2000;
+ net_set_timeout_handler(bootp_timeout, bootp_timeout_handler);
+ bootp_request();
+ }
+}
+
+#define put_vci(e, str) \
+ do { \
+ size_t vci_strlen = strlen(str); \
+ *e++ = 60; /* Vendor Class Identifier */ \
+ *e++ = vci_strlen; \
+ memcpy(e, str, vci_strlen); \
+ e += vci_strlen; \
+ } while (0)
+
+static u8 *add_vci(u8 *e)
+{
+ char *vci = NULL;
+ char *env_vci = env_get("bootp_vci");
+
+#if defined(CONFIG_XPL_BUILD) && defined(CONFIG_SPL_NET_VCI_STRING)
+ vci = CONFIG_SPL_NET_VCI_STRING;
+#elif defined(CONFIG_BOOTP_VCI_STRING)
+ vci = CONFIG_BOOTP_VCI_STRING;
+#endif
+
+ if (env_vci)
+ vci = env_vci;
+
+ if (vci)
+ put_vci(e, vci);
+
+ return e;
+}
+
+/*
+ * Initialize BOOTP extension fields in the request.
+ */
+#if defined(CONFIG_CMD_DHCP)
+static int dhcp_extended(u8 *e, int message_type, struct in_addr server_ip,
+ struct in_addr requested_ip)
+{
+ u8 *start = e;
+ u8 *cnt;
+#ifdef CONFIG_LIB_UUID
+ char *uuid;
+#endif
+ int clientarch = -1;
+
+#if defined(CONFIG_BOOTP_VENDOREX)
+ u8 *x;
+#endif
+#if defined(CONFIG_BOOTP_SEND_HOSTNAME)
+ char *hostname;
+#endif
+
+ *e++ = 99; /* RFC1048 Magic Cookie */
+ *e++ = 130;
+ *e++ = 83;
+ *e++ = 99;
+
+ *e++ = 53; /* DHCP Message Type */
+ *e++ = 1;
+ *e++ = message_type;
+
+ *e++ = 57; /* Maximum DHCP Message Size */
+ *e++ = 2;
+ *e++ = (576 - 312 + OPT_FIELD_SIZE) >> 8;
+ *e++ = (576 - 312 + OPT_FIELD_SIZE) & 0xff;
+
+ if (server_ip.s_addr) {
+ int tmp = ntohl(server_ip.s_addr);
+
+ *e++ = 54; /* ServerID */
+ *e++ = 4;
+ *e++ = tmp >> 24;
+ *e++ = tmp >> 16;
+ *e++ = tmp >> 8;
+ *e++ = tmp & 0xff;
+ }
+
+ if (requested_ip.s_addr) {
+ int tmp = ntohl(requested_ip.s_addr);
+
+ *e++ = 50; /* Requested IP */
+ *e++ = 4;
+ *e++ = tmp >> 24;
+ *e++ = tmp >> 16;
+ *e++ = tmp >> 8;
+ *e++ = tmp & 0xff;
+ }
+#if defined(CONFIG_BOOTP_SEND_HOSTNAME)
+ hostname = env_get("hostname");
+ if (hostname) {
+ int hostnamelen = strlen(hostname);
+
+ *e++ = 12; /* Hostname */
+ *e++ = hostnamelen;
+ memcpy(e, hostname, hostnamelen);
+ e += hostnamelen;
+ }
+#endif
+
+#ifdef CONFIG_BOOTP_PXE_CLIENTARCH
+ clientarch = CONFIG_BOOTP_PXE_CLIENTARCH;
+#endif
+
+ if (env_get("bootp_arch"))
+ clientarch = env_get_ulong("bootp_arch", 16, clientarch);
+
+ if (clientarch > 0) {
+ *e++ = 93; /* Client System Architecture */
+ *e++ = 2;
+ *e++ = (clientarch >> 8) & 0xff;
+ *e++ = clientarch & 0xff;
+ }
+
+ *e++ = 94; /* Client Network Interface Identifier */
+ *e++ = 3;
+ *e++ = 1; /* type field for UNDI */
+ *e++ = 0; /* major revision */
+ *e++ = 0; /* minor revision */
+
+#ifdef CONFIG_LIB_UUID
+ uuid = env_get("pxeuuid");
+
+ if (uuid) {
+ if (uuid_str_valid(uuid)) {
+ *e++ = 97; /* Client Machine Identifier */
+ *e++ = 17;
+ *e++ = 0; /* type 0 - UUID */
+
+ uuid_str_to_bin(uuid, e, UUID_STR_FORMAT_STD);
+ e += 16;
+ } else {
+ printf("Invalid pxeuuid: %s\n", uuid);
+ }
+ }
+#endif
+
+ e = add_vci(e);
+
+#if defined(CONFIG_BOOTP_VENDOREX)
+ x = dhcp_vendorex_prep(e);
+ if (x)
+ return x - start;
+#endif
+
+ *e++ = 55; /* Parameter Request List */
+ cnt = e++; /* Pointer to count of requested items */
+ *cnt = 0;
+#if defined(CONFIG_BOOTP_SUBNETMASK)
+ *e++ = 1; /* Subnet Mask */
+ *cnt += 1;
+#endif
+#if defined(CONFIG_BOOTP_TIMEOFFSET)
+ *e++ = 2;
+ *cnt += 1;
+#endif
+#if defined(CONFIG_BOOTP_GATEWAY)
+ *e++ = 3; /* Router Option */
+ *cnt += 1;
+#endif
+#if defined(CONFIG_BOOTP_DNS)
+ *e++ = 6; /* DNS Server(s) */
+ *cnt += 1;
+#endif
+#if defined(CONFIG_BOOTP_HOSTNAME)
+ *e++ = 12; /* Hostname */
+ *cnt += 1;
+#endif
+#if defined(CONFIG_BOOTP_BOOTFILESIZE)
+ *e++ = 13; /* Boot File Size */
+ *cnt += 1;
+#endif
+#if defined(CONFIG_BOOTP_BOOTPATH)
+ *e++ = 17; /* Boot path */
+ *cnt += 1;
+#endif
+#if defined(CONFIG_BOOTP_NISDOMAIN)
+ *e++ = 40; /* NIS Domain name request */
+ *cnt += 1;
+#endif
+#if defined(CONFIG_BOOTP_NTPSERVER)
+ *e++ = 42;
+ *cnt += 1;
+#endif
+ if (IS_ENABLED(CONFIG_BOOTP_PXE_DHCP_OPTION)) {
+ *e++ = 209; /* PXELINUX Config File */
+ *cnt += 1;
+ }
+ /* no options, so back up to avoid sending an empty request list */
+ if (*cnt == 0)
+ e -= 2;
+
+ *e++ = 255; /* End of the list */
+
+ /* Pad to minimal length */
+#ifdef CFG_DHCP_MIN_EXT_LEN
+ while ((e - start) < CFG_DHCP_MIN_EXT_LEN)
+ *e++ = 0;
+#endif
+
+ return e - start;
+}
+
+#else
+/*
+ * Warning: no field size check - change CONFIG_BOOTP_* at your own risk!
+ */
+static int bootp_extended(u8 *e)
+{
+ u8 *start = e;
+
+ *e++ = 99; /* RFC1048 Magic Cookie */
+ *e++ = 130;
+ *e++ = 83;
+ *e++ = 99;
+
+#if defined(CONFIG_CMD_DHCP)
+ *e++ = 53; /* DHCP Message Type */
+ *e++ = 1;
+ *e++ = DHCP_DISCOVER;
+
+ *e++ = 57; /* Maximum DHCP Message Size */
+ *e++ = 2;
+ *e++ = (576 - 312 + OPT_FIELD_SIZE) >> 16;
+ *e++ = (576 - 312 + OPT_FIELD_SIZE) & 0xff;
+#endif
+
+ e = add_vci(e);
+
+#if defined(CONFIG_BOOTP_SUBNETMASK)
+ *e++ = 1; /* Subnet mask request */
+ *e++ = 4;
+ e += 4;
+#endif
+
+#if defined(CONFIG_BOOTP_GATEWAY)
+ *e++ = 3; /* Default gateway request */
+ *e++ = 4;
+ e += 4;
+#endif
+
+#if defined(CONFIG_BOOTP_DNS)
+ *e++ = 6; /* Domain Name Server */
+ *e++ = 4;
+ e += 4;
+#endif
+
+#if defined(CONFIG_BOOTP_HOSTNAME)
+ *e++ = 12; /* Host name request */
+ *e++ = 32;
+ e += 32;
+#endif
+
+#if defined(CONFIG_BOOTP_BOOTFILESIZE)
+ *e++ = 13; /* Boot file size */
+ *e++ = 2;
+ e += 2;
+#endif
+
+#if defined(CONFIG_BOOTP_BOOTPATH)
+ *e++ = 17; /* Boot path */
+ *e++ = 32;
+ e += 32;
+#endif
+
+#if defined(CONFIG_BOOTP_NISDOMAIN)
+ *e++ = 40; /* NIS Domain name request */
+ *e++ = 32;
+ e += 32;
+#endif
+#if defined(CONFIG_BOOTP_NTPSERVER)
+ *e++ = 42;
+ *e++ = 4;
+ e += 4;
+#endif
+
+ *e++ = 255; /* End of the list */
+
+ /*
+ * If nothing in list, remove it altogether. Some DHCP servers get
+ * upset by this minor faux pas and do not respond at all.
+ */
+ if (e == start + 3) {
+ printf("*** Warning: no DHCP options requested\n");
+ e -= 3;
+ }
+
+ return e - start;
+}
+#endif
+
+void bootp_reset(void)
+{
+ bootp_num_ids = 0;
+ bootp_try = 0;
+ bootp_start = get_timer(0);
+ bootp_timeout = 250;
+}
+
+void bootp_request(void)
+{
+ uchar *pkt, *iphdr;
+ struct bootp_hdr *bp;
+ int extlen, pktlen, iplen;
+ int eth_hdr_size;
+#ifdef CONFIG_BOOTP_RANDOM_DELAY
+ ulong rand_ms;
+#endif
+ u32 bootp_id;
+ struct in_addr zero_ip;
+ struct in_addr bcast_ip;
+ char *ep; /* Environment pointer */
+
+ bootstage_mark_name(BOOTSTAGE_ID_BOOTP_START, "bootp_start");
+#if defined(CONFIG_CMD_DHCP)
+ dhcp_state = INIT;
+#endif
+
+ ep = env_get("bootpretryperiod");
+ if (ep != NULL)
+ time_taken_max = dectoul(ep, NULL);
+ else
+ time_taken_max = TIMEOUT_MS;
+
+#ifdef CONFIG_BOOTP_RANDOM_DELAY /* Random BOOTP delay */
+ if (bootp_try == 0)
+ srand_mac();
+
+ if (bootp_try <= 2) /* Start with max 1024 * 1ms */
+ rand_ms = rand() >> (22 - bootp_try);
+ else /* After 3rd BOOTP request max 8192 * 1ms */
+ rand_ms = rand() >> 19;
+
+ printf("Random delay: %ld ms...\n", rand_ms);
+ mdelay(rand_ms);
+
+#endif /* CONFIG_BOOTP_RANDOM_DELAY */
+
+ printf("BOOTP broadcast %d\n", ++bootp_try);
+ pkt = net_tx_packet;
+ memset((void *)pkt, 0, PKTSIZE);
+
+ eth_hdr_size = net_set_ether(pkt, net_bcast_ethaddr, PROT_IP);
+ pkt += eth_hdr_size;
+
+ /*
+ * Next line results in incorrect packet size being transmitted,
+ * resulting in errors in some DHCP servers, reporting missing bytes.
+ * Size must be set in packet header after extension length has been
+ * determined.
+ * C. Hallinan, DS4.COM, Inc.
+ */
+ /* net_set_udp_header(pkt, 0xFFFFFFFFL, PORT_BOOTPS, PORT_BOOTPC,
+ sizeof (struct bootp_hdr)); */
+ iphdr = pkt; /* We need this later for net_set_udp_header() */
+ pkt += IP_UDP_HDR_SIZE;
+
+ bp = (struct bootp_hdr *)pkt;
+ bp->bp_op = OP_BOOTREQUEST;
+ bp->bp_htype = HWT_ETHER;
+ bp->bp_hlen = HWL_ETHER;
+ bp->bp_hops = 0;
+ /*
+ * according to RFC1542, should be 0 on first request, secs since
+ * first request otherwise
+ */
+ bp->bp_secs = htons(get_timer(bootp_start) / 1000);
+ zero_ip.s_addr = 0;
+ net_write_ip(&bp->bp_ciaddr, zero_ip);
+ net_write_ip(&bp->bp_yiaddr, zero_ip);
+ net_write_ip(&bp->bp_siaddr, zero_ip);
+ net_write_ip(&bp->bp_giaddr, zero_ip);
+ memcpy(bp->bp_chaddr, net_ethaddr, 6);
+ copy_filename(bp->bp_file, net_boot_file_name, sizeof(bp->bp_file));
+
+ /* Request additional information from the BOOTP/DHCP server */
+#if defined(CONFIG_CMD_DHCP)
+ extlen = dhcp_extended((u8 *)bp->bp_vend, DHCP_DISCOVER, zero_ip,
+ zero_ip);
+#else
+ extlen = bootp_extended((u8 *)bp->bp_vend);
+#endif
+
+ /*
+ * Bootp ID is the lower 4 bytes of our ethernet address
+ * plus the current time in ms.
+ */
+ bootp_id = ((u32)net_ethaddr[2] << 24)
+ | ((u32)net_ethaddr[3] << 16)
+ | ((u32)net_ethaddr[4] << 8)
+ | (u32)net_ethaddr[5];
+ bootp_id += get_timer(0);
+ bootp_id = htonl(bootp_id);
+ bootp_add_id(bootp_id);
+ net_copy_u32(&bp->bp_id, &bootp_id);
+
+ /*
+ * Calculate proper packet lengths taking into account the
+ * variable size of the options field
+ */
+ iplen = BOOTP_HDR_SIZE - OPT_FIELD_SIZE + extlen;
+ pktlen = eth_hdr_size + IP_UDP_HDR_SIZE + iplen;
+ bcast_ip.s_addr = 0xFFFFFFFFL;
+ net_set_udp_header(iphdr, bcast_ip, PORT_BOOTPS, PORT_BOOTPC, iplen);
+ net_set_timeout_handler(bootp_timeout, bootp_timeout_handler);
+
+#if defined(CONFIG_CMD_DHCP)
+ dhcp_state = SELECTING;
+ net_set_udp_handler(dhcp_handler);
+#else
+ net_set_udp_handler(bootp_handler);
+#endif
+ net_send_packet(net_tx_packet, pktlen);
+}
+
+#if defined(CONFIG_CMD_DHCP)
+static void dhcp_process_options(uchar *popt, uchar *end)
+{
+ int oplen, size;
+#if defined(CONFIG_CMD_SNTP) && defined(CONFIG_BOOTP_TIMEOFFSET)
+ int *to_ptr;
+#endif
+
+ while (popt < end && *popt != 0xff) {
+ oplen = *(popt + 1);
+ switch (*popt) {
+ case 0:
+ oplen = -1; /* Pad omits len byte */
+ break;
+ case 1:
+ net_copy_ip(&net_netmask, (popt + 2));
+ break;
+#if defined(CONFIG_CMD_SNTP) && defined(CONFIG_BOOTP_TIMEOFFSET)
+ case 2: /* Time offset */
+ to_ptr = &net_ntp_time_offset;
+ net_copy_u32((u32 *)to_ptr, (u32 *)(popt + 2));
+ net_ntp_time_offset = ntohl(net_ntp_time_offset);
+ break;
+#endif
+ case 3:
+ net_copy_ip(&net_gateway, (popt + 2));
+ break;
+ case 6:
+ net_copy_ip(&net_dns_server, (popt + 2));
+#if defined(CONFIG_BOOTP_DNS2)
+ if (*(popt + 1) > 4)
+ net_copy_ip(&net_dns_server2, (popt + 2 + 4));
+#endif
+ break;
+ case 12:
+ size = truncate_sz("Host Name",
+ sizeof(net_hostname), oplen);
+ memcpy(&net_hostname, popt + 2, size);
+ net_hostname[size] = 0;
+ break;
+ case 15: /* Ignore Domain Name Option */
+ break;
+ case 17:
+ size = truncate_sz("Root Path",
+ sizeof(net_root_path), oplen);
+ memcpy(&net_root_path, popt + 2, size);
+ net_root_path[size] = 0;
+ break;
+ case 28: /* Ignore Broadcast Address Option */
+ break;
+ case 40: /* NIS Domain name */
+ if (net_nis_domain[0] == 0) {
+ size = truncate_sz("NIS Domain Name",
+ sizeof(net_nis_domain), oplen);
+ memcpy(&net_nis_domain, popt + 2, size);
+ net_nis_domain[size] = 0;
+ }
+ break;
+#if defined(CONFIG_CMD_SNTP) && defined(CONFIG_BOOTP_NTPSERVER)
+ case 42: /* NTP server IP */
+ net_copy_ip(&net_ntp_server, (popt + 2));
+ break;
+#endif
+ case 51:
+ net_copy_u32(&dhcp_leasetime, (u32 *)(popt + 2));
+ break;
+ case 52:
+ dhcp_option_overload = popt[2];
+ break;
+ case 53: /* Ignore Message Type Option */
+ break;
+ case 54:
+ net_copy_ip(&dhcp_server_ip, (popt + 2));
+ break;
+ case 58: /* Ignore Renewal Time Option */
+ break;
+ case 59: /* Ignore Rebinding Time Option */
+ break;
+ case 66: /* Ignore TFTP server name */
+ break;
+ case 67: /* Bootfile option */
+ if (!net_boot_file_name_explicit) {
+ size = truncate_sz("Bootfile",
+ sizeof(net_boot_file_name),
+ oplen);
+ memcpy(&net_boot_file_name, popt + 2, size);
+ net_boot_file_name[size] = 0;
+ }
+ break;
+ case 209: /* PXELINUX Config File */
+ if (IS_ENABLED(CONFIG_BOOTP_PXE_DHCP_OPTION)) {
+ /* In case it has already been allocated when get DHCP Offer packet,
+ * free first to avoid memory leak.
+ */
+ if (pxelinux_configfile)
+ free(pxelinux_configfile);
+
+ pxelinux_configfile = (char *)malloc((oplen + 1) * sizeof(char));
+
+ if (pxelinux_configfile)
+ strlcpy(pxelinux_configfile, popt + 2, oplen + 1);
+ else
+ printf("Error: Failed to allocate pxelinux_configfile\n");
+ }
+ break;
+ default:
+#if defined(CONFIG_BOOTP_VENDOREX)
+ if (dhcp_vendorex_proc(popt))
+ break;
+#endif
+ printf("*** Unhandled DHCP Option in OFFER/ACK:"
+ " %d\n", *popt);
+ break;
+ }
+ popt += oplen + 2; /* Process next option */
+ }
+}
+
+static void dhcp_packet_process_options(struct bootp_hdr *bp)
+{
+ uchar *popt = (uchar *)&bp->bp_vend[4];
+ uchar *end = popt + BOOTP_HDR_SIZE;
+
+ if (net_read_u32((u32 *)&bp->bp_vend[0]) != htonl(BOOTP_VENDOR_MAGIC))
+ return;
+
+ dhcp_option_overload = 0;
+
+ /*
+ * The 'options' field MUST be interpreted first, 'file' next,
+ * 'sname' last.
+ */
+ dhcp_process_options(popt, end);
+
+ if (dhcp_option_overload & OVERLOAD_FILE) {
+ popt = (uchar *)bp->bp_file;
+ end = popt + sizeof(bp->bp_file);
+ dhcp_process_options(popt, end);
+ }
+
+ if (dhcp_option_overload & OVERLOAD_SNAME) {
+ popt = (uchar *)bp->bp_sname;
+ end = popt + sizeof(bp->bp_sname);
+ dhcp_process_options(popt, end);
+ }
+}
+
+static int dhcp_message_type(unsigned char *popt)
+{
+ if (net_read_u32((u32 *)popt) != htonl(BOOTP_VENDOR_MAGIC))
+ return -1;
+
+ popt += 4;
+ while (*popt != 0xff) {
+ if (*popt == 53) /* DHCP Message Type */
+ return *(popt + 2);
+ if (*popt == 0) {
+ /* Pad */
+ popt += 1;
+ } else {
+ /* Scan through all options */
+ popt += *(popt + 1) + 2;
+ }
+ }
+ return -1;
+}
+
+static void dhcp_send_request_packet(struct bootp_hdr *bp_offer)
+{
+ uchar *pkt, *iphdr;
+ struct bootp_hdr *bp;
+ int pktlen, iplen, extlen;
+ int eth_hdr_size;
+ struct in_addr offered_ip;
+ struct in_addr zero_ip;
+ struct in_addr bcast_ip;
+
+ debug("dhcp_send_request_packet: Sending DHCPREQUEST\n");
+ pkt = net_tx_packet;
+ memset((void *)pkt, 0, PKTSIZE);
+
+ eth_hdr_size = net_set_ether(pkt, net_bcast_ethaddr, PROT_IP);
+ pkt += eth_hdr_size;
+
+ iphdr = pkt; /* We'll need this later to set proper pkt size */
+ pkt += IP_UDP_HDR_SIZE;
+
+ bp = (struct bootp_hdr *)pkt;
+ bp->bp_op = OP_BOOTREQUEST;
+ bp->bp_htype = HWT_ETHER;
+ bp->bp_hlen = HWL_ETHER;
+ bp->bp_hops = 0;
+ bp->bp_secs = htons(get_timer(bootp_start) / 1000);
+ /* Do not set the client IP, your IP, or server IP yet, since it
+ * hasn't been ACK'ed by the server yet */
+
+ /*
+ * RFC3046 requires Relay Agents to discard packets with
+ * nonzero and offered giaddr
+ */
+ zero_ip.s_addr = 0;
+ net_write_ip(&bp->bp_giaddr, zero_ip);
+
+ memcpy(bp->bp_chaddr, net_ethaddr, 6);
+ copy_filename(bp->bp_file, net_boot_file_name, sizeof(bp->bp_file));
+
+ /*
+ * ID is the id of the OFFER packet
+ */
+
+ net_copy_u32(&bp->bp_id, &bp_offer->bp_id);
+
+ /*
+ * Copy options from OFFER packet if present
+ */
+
+ /* Copy offered IP into the parameters request list */
+ net_copy_ip(&offered_ip, &bp_offer->bp_yiaddr);
+ extlen = dhcp_extended((u8 *)bp->bp_vend, DHCP_REQUEST,
+ dhcp_server_ip, offered_ip);
+
+ iplen = BOOTP_HDR_SIZE - OPT_FIELD_SIZE + extlen;
+ pktlen = eth_hdr_size + IP_UDP_HDR_SIZE + iplen;
+ bcast_ip.s_addr = 0xFFFFFFFFL;
+ net_set_udp_header(iphdr, bcast_ip, PORT_BOOTPS, PORT_BOOTPC, iplen);
+
+ debug("Transmitting DHCPREQUEST packet: len = %d\n", pktlen);
+ net_send_packet(net_tx_packet, pktlen);
+}
+
+/*
+ * Handle DHCP received packets.
+ */
+static void dhcp_handler(uchar *pkt, unsigned dest, struct in_addr sip,
+ unsigned src, unsigned len)
+{
+ struct bootp_hdr *bp = (struct bootp_hdr *)pkt;
+
+ debug("DHCPHandler: got packet: (src=%d, dst=%d, len=%d) state: %d\n",
+ src, dest, len, dhcp_state);
+
+ /* Filter out pkts we don't want */
+ if (check_reply_packet(pkt, dest, src, len))
+ return;
+
+ debug("DHCPHandler: got DHCP packet: (src=%d, dst=%d, len=%d) state: "
+ "%d\n", src, dest, len, dhcp_state);
+
+ if (net_read_ip(&bp->bp_yiaddr).s_addr == 0) {
+#if defined(CONFIG_SERVERIP_FROM_PROXYDHCP)
+ store_bootp_params(bp);
+#endif
+ return;
+ }
+
+ switch (dhcp_state) {
+ case SELECTING:
+ /*
+ * Wait an appropriate time for any potential DHCPOFFER packets
+ * to arrive. Then select one, and generate DHCPREQUEST
+ * response. If filename is in format we recognize, assume it
+ * is a valid OFFER from a server we want.
+ */
+ debug("DHCP: state=SELECTING bp_file: \"%s\"\n", bp->bp_file);
+#ifdef CONFIG_SYS_BOOTFILE_PREFIX
+ if (strncmp(bp->bp_file,
+ CONFIG_SYS_BOOTFILE_PREFIX,
+ strlen(CONFIG_SYS_BOOTFILE_PREFIX)) == 0) {
+#endif /* CONFIG_SYS_BOOTFILE_PREFIX */
+ if (CONFIG_IS_ENABLED(UNIT_TEST) &&
+ dhcp_message_type((u8 *)bp->bp_vend) == -1) {
+ debug("got BOOTP response; transitioning to BOUND\n");
+ goto dhcp_got_bootp;
+ }
+ dhcp_packet_process_options(bp);
+ if (CONFIG_IS_ENABLED(EFI_LOADER) &&
+ IS_ENABLED(CONFIG_NETDEVICES))
+ efi_net_set_dhcp_ack(pkt, len);
+
+#if defined(CONFIG_SERVERIP_FROM_PROXYDHCP)
+ if (!net_server_ip.s_addr)
+ udelay(CONFIG_SERVERIP_FROM_PROXYDHCP_DELAY_MS *
+ 1000);
+#endif /* CONFIG_SERVERIP_FROM_PROXYDHCP */
+
+ debug("TRANSITIONING TO REQUESTING STATE\n");
+ dhcp_state = REQUESTING;
+
+ net_set_timeout_handler(5000, bootp_timeout_handler);
+ dhcp_send_request_packet(bp);
+#ifdef CONFIG_SYS_BOOTFILE_PREFIX
+ }
+#endif /* CONFIG_SYS_BOOTFILE_PREFIX */
+
+ return;
+ break;
+ case REQUESTING:
+ debug("DHCP State: REQUESTING\n");
+
+ if (dhcp_message_type((u8 *)bp->bp_vend) == DHCP_ACK) {
+dhcp_got_bootp:
+ dhcp_packet_process_options(bp);
+ /* Store net params from reply */
+ store_net_params(bp);
+ dhcp_state = BOUND;
+ printf("DHCP client bound to address %pI4 (%lu ms)\n",
+ &net_ip, get_timer(bootp_start));
+ net_set_timeout_handler(0, (thand_f *)0);
+ bootstage_mark_name(BOOTSTAGE_ID_BOOTP_STOP,
+ "bootp_stop");
+
+ net_auto_load();
+ return;
+ }
+ break;
+ case BOUND:
+ /* DHCP client bound to address */
+ break;
+ default:
+ puts("DHCP: INVALID STATE\n");
+ break;
+ }
+}
+
+void dhcp_request(void)
+{
+ bootp_request();
+}
+#endif /* CONFIG_CMD_DHCP */
diff --git a/net/bootp.h b/net/bootp.h
new file mode 100644
index 00000000000..521d38f3528
--- /dev/null
+++ b/net/bootp.h
@@ -0,0 +1,95 @@
+/*
+ * Copied from LiMon - BOOTP.
+ *
+ * Copyright 1994, 1995, 2000 Neil Russell.
+ * (See License)
+ * Copyright 2000 Paolo Scaffardi
+ */
+
+#ifndef __BOOTP_H__
+#define __BOOTP_H__
+
+#ifndef __NET_H__
+#include <net.h>
+#endif /* __NET_H__ */
+
+/**********************************************************************/
+
+#define PORT_BOOTPS 67 /* BOOTP server UDP port */
+#define PORT_BOOTPC 68 /* BOOTP client UDP port */
+
+/*
+ * BOOTP header.
+ */
+#if defined(CONFIG_CMD_DHCP)
+/* Minimum DHCP Options size per RFC2131 - results in 576 byte pkt */
+#define OPT_FIELD_SIZE 312
+#if defined(CONFIG_BOOTP_VENDOREX)
+extern u8 *dhcp_vendorex_prep(u8 *e); /*rtn new e after add own opts. */
+extern u8 *dhcp_vendorex_proc(u8 *e); /*rtn next e if mine,else NULL */
+#endif
+#else
+#define OPT_FIELD_SIZE 64
+#endif
+
+struct bootp_hdr {
+ u8 bp_op; /* Operation */
+# define OP_BOOTREQUEST 1
+# define OP_BOOTREPLY 2
+ u8 bp_htype; /* Hardware type */
+# define HWT_ETHER 1
+ u8 bp_hlen; /* Hardware address length */
+# define HWL_ETHER 6
+ u8 bp_hops; /* Hop count (gateway thing) */
+ u32 bp_id; /* Transaction ID */
+ u16 bp_secs; /* Seconds since boot */
+ u16 bp_spare1; /* Alignment */
+ struct in_addr bp_ciaddr; /* Client IP address */
+ struct in_addr bp_yiaddr; /* Your (client) IP address */
+ struct in_addr bp_siaddr; /* Server IP address */
+ struct in_addr bp_giaddr; /* Gateway IP address */
+ u8 bp_chaddr[16]; /* Client hardware address */
+ char bp_sname[64]; /* Server host name */
+ char bp_file[128]; /* Boot file name */
+ char bp_vend[OPT_FIELD_SIZE]; /* Vendor information */
+} __attribute__((packed));
+
+#define BOOTP_HDR_SIZE sizeof(struct bootp_hdr)
+
+/**********************************************************************/
+/*
+ * Global functions and variables.
+ */
+
+/* bootp.c */
+extern u32 bootp_id; /* ID of cur BOOTP request */
+extern int bootp_try;
+
+/* Send a BOOTP request */
+void bootp_reset(void);
+void bootp_request(void);
+
+/****************** DHCP Support *********************/
+void dhcp_request(void);
+
+/* DHCP States */
+typedef enum { INIT,
+ INIT_REBOOT,
+ REBOOTING,
+ SELECTING,
+ REQUESTING,
+ REBINDING,
+ BOUND,
+ RENEWING } dhcp_state_t;
+
+#define DHCP_DISCOVER 1
+#define DHCP_OFFER 2
+#define DHCP_REQUEST 3
+#define DHCP_DECLINE 4
+#define DHCP_ACK 5
+#define DHCP_NAK 6
+#define DHCP_RELEASE 7
+
+/**********************************************************************/
+
+#endif /* __BOOTP_H__ */
diff --git a/net/cdp.c b/net/cdp.c
new file mode 100644
index 00000000000..d4cfc587ee3
--- /dev/null
+++ b/net/cdp.c
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copied from Linux Monitor (LiMon) - Networking.
+ *
+ * Copyright 1994 - 2000 Neil Russell.
+ * (See License)
+ * Copyright 2000 Roland Borde
+ * Copyright 2000 Paolo Scaffardi
+ * Copyright 2000-2002 Wolfgang Denk, wd@denx.de
+ */
+
+#include <net.h>
+
+#include "cdp.h"
+
+/* Ethernet bcast address */
+const u8 net_cdp_ethaddr[6] = { 0x01, 0x00, 0x0c, 0xcc, 0xcc, 0xcc };
+
+#define CDP_DEVICE_ID_TLV 0x0001
+#define CDP_ADDRESS_TLV 0x0002
+#define CDP_PORT_ID_TLV 0x0003
+#define CDP_CAPABILITIES_TLV 0x0004
+#define CDP_VERSION_TLV 0x0005
+#define CDP_PLATFORM_TLV 0x0006
+#define CDP_NATIVE_VLAN_TLV 0x000a
+#define CDP_APPLIANCE_VLAN_TLV 0x000e
+#define CDP_TRIGGER_TLV 0x000f
+#define CDP_POWER_CONSUMPTION_TLV 0x0010
+#define CDP_SYSNAME_TLV 0x0014
+#define CDP_SYSOBJECT_TLV 0x0015
+#define CDP_MANAGEMENT_ADDRESS_TLV 0x0016
+
+#define CDP_TIMEOUT 250UL /* one packet every 250ms */
+
+static int cdp_seq;
+static int cdp_ok;
+
+ushort cdp_native_vlan;
+ushort cdp_appliance_vlan;
+
+static const uchar cdp_snap_hdr[8] = {
+ 0xAA, 0xAA, 0x03, 0x00, 0x00, 0x0C, 0x20, 0x00 };
+
+static ushort cdp_compute_csum(const uchar *buff, ushort len)
+{
+ ushort csum;
+ int odd;
+ ulong result = 0;
+ ushort leftover;
+ ushort *p;
+
+ if (len > 0) {
+ odd = 1 & (ulong)buff;
+ if (odd) {
+ result = *buff << 8;
+ len--;
+ buff++;
+ }
+ while (len > 1) {
+ p = (ushort *)buff;
+ result += *p++;
+ buff = (uchar *)p;
+ if (result & 0x80000000)
+ result = (result & 0xFFFF) + (result >> 16);
+ len -= 2;
+ }
+ if (len) {
+ leftover = (signed short)(*(const signed char *)buff);
+ /*
+ * CISCO SUCKS big time! (and blows too):
+ * CDP uses the IP checksum algorithm with a twist;
+ * for the last byte it *sign* extends and sums.
+ */
+ result = (result & 0xffff0000) |
+ ((result + leftover) & 0x0000ffff);
+ }
+ while (result >> 16)
+ result = (result & 0xFFFF) + (result >> 16);
+
+ if (odd)
+ result = ((result >> 8) & 0xff) |
+ ((result & 0xff) << 8);
+ }
+
+ /* add up 16-bit and 17-bit words for 17+c bits */
+ result = (result & 0xffff) + (result >> 16);
+ /* add up 16-bit and 2-bit for 16+c bit */
+ result = (result & 0xffff) + (result >> 16);
+ /* add up carry.. */
+ result = (result & 0xffff) + (result >> 16);
+
+ /* negate */
+ csum = ~(ushort)result;
+
+ /* run time endian detection */
+ if (csum != htons(csum)) /* little endian */
+ csum = htons(csum);
+
+ return csum;
+}
+
+static int cdp_send_trigger(void)
+{
+ uchar *pkt;
+ ushort *s;
+ ushort *cp;
+ struct ethernet_hdr *et;
+ int len;
+ ushort chksum;
+#if defined(CONFIG_CDP_DEVICE_ID) || defined(CONFIG_CDP_PORT_ID) || \
+ defined(CONFIG_CDP_VERSION) || defined(CONFIG_CDP_PLATFORM)
+ char buf[32];
+#endif
+
+ pkt = net_tx_packet;
+ et = (struct ethernet_hdr *)pkt;
+
+ /* NOTE: trigger sent not on any VLAN */
+
+ /* form ethernet header */
+ memcpy(et->et_dest, net_cdp_ethaddr, 6);
+ memcpy(et->et_src, net_ethaddr, 6);
+
+ pkt += ETHER_HDR_SIZE;
+
+ /* SNAP header */
+ memcpy((uchar *)pkt, cdp_snap_hdr, sizeof(cdp_snap_hdr));
+ pkt += sizeof(cdp_snap_hdr);
+
+ /* CDP header */
+ *pkt++ = 0x02; /* CDP version 2 */
+ *pkt++ = 180; /* TTL */
+ s = (ushort *)pkt;
+ cp = s;
+ /* checksum (0 for later calculation) */
+ *s++ = htons(0);
+
+ /* CDP fields */
+#ifdef CONFIG_CDP_DEVICE_ID
+ *s++ = htons(CDP_DEVICE_ID_TLV);
+ *s++ = htons(CONFIG_CDP_DEVICE_ID);
+ sprintf(buf, CONFIG_CDP_DEVICE_ID_PREFIX "%pm", net_ethaddr);
+ memcpy((uchar *)s, buf, 16);
+ s += 16 / 2;
+#endif
+
+#ifdef CONFIG_CDP_PORT_ID
+ *s++ = htons(CDP_PORT_ID_TLV);
+ memset(buf, 0, sizeof(buf));
+ sprintf(buf, CONFIG_CDP_PORT_ID, eth_get_dev_index());
+ len = strlen(buf);
+ if (len & 1) /* make it even */
+ len++;
+ *s++ = htons(len + 4);
+ memcpy((uchar *)s, buf, len);
+ s += len / 2;
+#endif
+
+#ifdef CONFIG_CDP_CAPABILITIES
+ *s++ = htons(CDP_CAPABILITIES_TLV);
+ *s++ = htons(8);
+ *(ulong *)s = htonl(CONFIG_CDP_CAPABILITIES);
+ s += 2;
+#endif
+
+#ifdef CONFIG_CDP_VERSION
+ *s++ = htons(CDP_VERSION_TLV);
+ memset(buf, 0, sizeof(buf));
+ strcpy(buf, CONFIG_CDP_VERSION);
+ len = strlen(buf);
+ if (len & 1) /* make it even */
+ len++;
+ *s++ = htons(len + 4);
+ memcpy((uchar *)s, buf, len);
+ s += len / 2;
+#endif
+
+#ifdef CONFIG_CDP_PLATFORM
+ *s++ = htons(CDP_PLATFORM_TLV);
+ memset(buf, 0, sizeof(buf));
+ strcpy(buf, CONFIG_CDP_PLATFORM);
+ len = strlen(buf);
+ if (len & 1) /* make it even */
+ len++;
+ *s++ = htons(len + 4);
+ memcpy((uchar *)s, buf, len);
+ s += len / 2;
+#endif
+
+#ifdef CONFIG_CDP_TRIGGER
+ *s++ = htons(CDP_TRIGGER_TLV);
+ *s++ = htons(8);
+ *(ulong *)s = htonl(CONFIG_CDP_TRIGGER);
+ s += 2;
+#endif
+
+#ifdef CONFIG_CDP_POWER_CONSUMPTION
+ *s++ = htons(CDP_POWER_CONSUMPTION_TLV);
+ *s++ = htons(6);
+ *s++ = htons(CONFIG_CDP_POWER_CONSUMPTION);
+#endif
+
+ /* length of ethernet packet */
+ len = (uchar *)s - ((uchar *)net_tx_packet + ETHER_HDR_SIZE);
+ et->et_protlen = htons(len);
+
+ len = ETHER_HDR_SIZE + sizeof(cdp_snap_hdr);
+ chksum = cdp_compute_csum((uchar *)net_tx_packet + len,
+ (uchar *)s - (net_tx_packet + len));
+ if (chksum == 0)
+ chksum = 0xFFFF;
+ *cp = htons(chksum);
+
+ net_send_packet(net_tx_packet, (uchar *)s - net_tx_packet);
+ return 0;
+}
+
+static void cdp_timeout_handler(void)
+{
+ cdp_seq++;
+
+ if (cdp_seq < 3) {
+ net_set_timeout_handler(CDP_TIMEOUT, cdp_timeout_handler);
+ cdp_send_trigger();
+ return;
+ }
+
+ /* if not OK try again */
+ if (!cdp_ok)
+ net_start_again();
+ else
+ net_set_state(NETLOOP_SUCCESS);
+}
+
+void cdp_receive(const uchar *pkt, unsigned len)
+{
+ const uchar *t;
+ const ushort *ss;
+ ushort type, tlen;
+ ushort vlan, nvlan;
+
+ /* minimum size? */
+ if (len < sizeof(cdp_snap_hdr) + 4)
+ goto pkt_short;
+
+ /* check for valid CDP SNAP header */
+ if (memcmp(pkt, cdp_snap_hdr, sizeof(cdp_snap_hdr)) != 0)
+ return;
+
+ pkt += sizeof(cdp_snap_hdr);
+ len -= sizeof(cdp_snap_hdr);
+
+ /* Version of CDP protocol must be >= 2 and TTL != 0 */
+ if (pkt[0] < 0x02 || pkt[1] == 0)
+ return;
+
+ /*
+ * if version is greater than 0x02 maybe we'll have a problem;
+ * output a warning
+ */
+ if (pkt[0] != 0x02)
+ printf("**WARNING: CDP packet received with a protocol version "
+ "%d > 2\n", pkt[0] & 0xff);
+
+ if (cdp_compute_csum(pkt, len) != 0)
+ return;
+
+ pkt += 4;
+ len -= 4;
+
+ vlan = htons(-1);
+ nvlan = htons(-1);
+ while (len > 0) {
+ if (len < 4)
+ goto pkt_short;
+
+ ss = (const ushort *)pkt;
+ type = ntohs(ss[0]);
+ tlen = ntohs(ss[1]);
+ if (tlen > len)
+ goto pkt_short;
+
+ pkt += tlen;
+ len -= tlen;
+
+ ss += 2; /* point ss to the data of the TLV */
+ tlen -= 4;
+
+ switch (type) {
+ case CDP_DEVICE_ID_TLV:
+ break;
+ case CDP_ADDRESS_TLV:
+ break;
+ case CDP_PORT_ID_TLV:
+ break;
+ case CDP_CAPABILITIES_TLV:
+ break;
+ case CDP_VERSION_TLV:
+ break;
+ case CDP_PLATFORM_TLV:
+ break;
+ case CDP_NATIVE_VLAN_TLV:
+ nvlan = *ss;
+ break;
+ case CDP_APPLIANCE_VLAN_TLV:
+ t = (const uchar *)ss;
+ while (tlen > 0) {
+ if (tlen < 3)
+ goto pkt_short;
+
+ ss = (const ushort *)(t + 1);
+
+#ifdef CONFIG_CDP_APPLIANCE_VLAN_TYPE
+ if (t[0] == CONFIG_CDP_APPLIANCE_VLAN_TYPE)
+ vlan = *ss;
+#else
+ /* XXX will this work; dunno */
+ vlan = ntohs(*ss);
+#endif
+ t += 3; tlen -= 3;
+ }
+ break;
+ case CDP_TRIGGER_TLV:
+ break;
+ case CDP_POWER_CONSUMPTION_TLV:
+ break;
+ case CDP_SYSNAME_TLV:
+ break;
+ case CDP_SYSOBJECT_TLV:
+ break;
+ case CDP_MANAGEMENT_ADDRESS_TLV:
+ break;
+ }
+ }
+
+ cdp_appliance_vlan = vlan;
+ cdp_native_vlan = nvlan;
+
+ cdp_ok = 1;
+ return;
+
+pkt_short:
+ printf("** CDP packet is too short\n");
+ return;
+}
+
+void cdp_start(void)
+{
+ printf("Using %s device\n", eth_get_name());
+ cdp_seq = 0;
+ cdp_ok = 0;
+
+ cdp_native_vlan = htons(-1);
+ cdp_appliance_vlan = htons(-1);
+
+ net_set_timeout_handler(CDP_TIMEOUT, cdp_timeout_handler);
+
+ cdp_send_trigger();
+}
diff --git a/net/cdp.h b/net/cdp.h
new file mode 100644
index 00000000000..16ccbf4b59e
--- /dev/null
+++ b/net/cdp.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copied from Linux Monitor (LiMon) - Networking.
+ *
+ * Copyright 1994 - 2000 Neil Russell.
+ * (See License)
+ * Copyright 2000 Roland Borde
+ * Copyright 2000 Paolo Scaffardi
+ * Copyright 2000-2002 Wolfgang Denk, wd@denx.de
+ */
+
+#if defined(CONFIG_CMD_CDP)
+
+#ifndef __CDP_H__
+#define __CDP_H__
+
+void cdp_start(void);
+/* Process a received CDP packet */
+void cdp_receive(const uchar *pkt, unsigned len);
+
+#endif /* __CDP_H__ */
+#endif
diff --git a/net/dhcpv6.c b/net/dhcpv6.c
new file mode 100644
index 00000000000..54619ee6983
--- /dev/null
+++ b/net/dhcpv6.c
@@ -0,0 +1,732 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) Microsoft Corporation
+ * Author: Sean Edmond <seanedmond@microsoft.com>
+ *
+ */
+
+/* Simple DHCP6 network layer implementation. */
+
+#include <net6.h>
+#include <malloc.h>
+#include <linux/delay.h>
+#include "net_rand.h"
+#include "dhcpv6.h"
+
+#define PORT_DHCP6_S 547 /* DHCP6 server UDP port */
+#define PORT_DHCP6_C 546 /* DHCP6 client UDP port */
+
+/* default timeout parameters (in ms) */
+#define SOL_MAX_DELAY_MS 1000
+#define SOL_TIMEOUT_MS 1000
+#define SOL_MAX_RT_MS 3600000
+#define REQ_TIMEOUT_MS 1000
+#define REQ_MAX_RT_MS 30000
+#define REQ_MAX_RC 10
+#define MAX_WAIT_TIME_MS 60000
+
+/* global variable to track any updates from DHCP6 server */
+int updated_sol_max_rt_ms = SOL_MAX_RT_MS;
+/* state machine parameters/variables */
+struct dhcp6_sm_params sm_params;
+
+static void dhcp6_state_machine(bool timeout, uchar *rx_pkt, unsigned int len);
+
+/* Handle DHCP received packets (set as UDP handler) */
+static void dhcp6_handler(uchar *pkt, unsigned int dest, struct in_addr sip,
+ unsigned int src, unsigned int len)
+{
+ /* return if ports don't match DHCPv6 ports */
+ if (dest != PORT_DHCP6_C || src != PORT_DHCP6_S)
+ return;
+
+ dhcp6_state_machine(false, pkt, len);
+}
+
+/**
+ * dhcp6_add_option() - Adds DHCP6 option to a packet
+ * @option_id: The option ID to add (See DHCP6_OPTION_* definitions)
+ * @pkt: A pointer to the current write location of the TX packet
+ *
+ * Return: The number of bytes written into "*pkt"
+ */
+static int dhcp6_add_option(int option_id, uchar *pkt)
+{
+ struct dhcp6_option_duid_ll *duid_opt;
+ struct dhcp6_option_elapsed_time *elapsed_time_opt;
+ struct dhcp6_option_ia_ta *ia_ta_opt;
+ struct dhcp6_option_ia_na *ia_na_opt;
+ struct dhcp6_option_oro *oro_opt;
+ struct dhcp6_option_client_arch *client_arch_opt;
+ struct dhcp6_option_vendor_class *vendor_class_opt;
+ int opt_len;
+ long elapsed_time;
+ size_t vci_strlen;
+ int num_oro = 0;
+ int num_client_arch = 0;
+ int num_vc_data = 0;
+ struct dhcp6_option_hdr *dhcp_option = (struct dhcp6_option_hdr *)pkt;
+ uchar *dhcp_option_start = pkt + sizeof(struct dhcp6_option_hdr);
+
+ dhcp_option->option_id = htons(option_id);
+
+ switch (option_id) {
+ case DHCP6_OPTION_CLIENTID:
+ /* Only support for DUID-LL in Client ID option for now */
+ duid_opt = (struct dhcp6_option_duid_ll *)dhcp_option_start;
+ duid_opt->duid_type = htons(DUID_TYPE_LL);
+ duid_opt->hw_type = htons(DUID_HW_TYPE_ENET);
+ memcpy(duid_opt->ll_addr, net_ethaddr, ETH_ALEN);
+ opt_len = sizeof(struct dhcp6_option_duid_ll) + ETH_ALEN;
+
+ /* Save DUID for comparison later */
+ memcpy(sm_params.duid, duid_opt, opt_len);
+ break;
+ case DHCP6_OPTION_ELAPSED_TIME:
+ /* calculate elapsed time in 1/100th of a second */
+ elapsed_time = (sm_params.dhcp6_retry_ms -
+ sm_params.dhcp6_start_ms) / 10;
+ if (elapsed_time > 0xFFFF)
+ elapsed_time = 0xFFFF;
+
+ elapsed_time_opt = (struct dhcp6_option_elapsed_time *)dhcp_option_start;
+ elapsed_time_opt->elapsed_time = htons(elapsed_time);
+
+ opt_len = sizeof(struct dhcp6_option_elapsed_time);
+ break;
+ case DHCP6_OPTION_IA_TA:
+ ia_ta_opt = (struct dhcp6_option_ia_ta *)dhcp_option_start;
+ ia_ta_opt->iaid = htonl(sm_params.ia_id);
+
+ opt_len = sizeof(struct dhcp6_option_ia_ta);
+ break;
+ case DHCP6_OPTION_IA_NA:
+ ia_na_opt = (struct dhcp6_option_ia_na *)dhcp_option_start;
+ ia_na_opt->iaid = htonl(sm_params.ia_id);
+ /* In a message sent by a client to a server,
+ * the T1 and T2 fields SHOULD be set to 0
+ */
+ ia_na_opt->t1 = 0;
+ ia_na_opt->t2 = 0;
+
+ opt_len = sizeof(struct dhcp6_option_ia_na);
+ break;
+ case DHCP6_OPTION_ORO:
+ oro_opt = (struct dhcp6_option_oro *)dhcp_option_start;
+ oro_opt->req_option_code[num_oro++] = htons(DHCP6_OPTION_OPT_BOOTFILE_URL);
+ oro_opt->req_option_code[num_oro++] = htons(DHCP6_OPTION_SOL_MAX_RT);
+ if (IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION)) {
+ oro_opt->req_option_code[num_oro++] =
+ htons(DHCP6_OPTION_OPT_BOOTFILE_PARAM);
+ }
+
+ opt_len = sizeof(__be16) * num_oro;
+ break;
+ case DHCP6_OPTION_CLIENT_ARCH_TYPE:
+ client_arch_opt = (struct dhcp6_option_client_arch *)dhcp_option_start;
+ client_arch_opt->arch_type[num_client_arch++] = htons(CONFIG_DHCP6_PXE_CLIENTARCH);
+
+ opt_len = sizeof(__be16) * num_client_arch;
+ break;
+ case DHCP6_OPTION_VENDOR_CLASS:
+ vendor_class_opt = (struct dhcp6_option_vendor_class *)dhcp_option_start;
+ vendor_class_opt->enterprise_number = htonl(CONFIG_DHCP6_ENTERPRISE_ID);
+
+ vci_strlen = strlen(DHCP6_VCI_STRING);
+ vendor_class_opt->vendor_class_data[num_vc_data].vendor_class_len =
+ htons(vci_strlen);
+ memcpy(vendor_class_opt->vendor_class_data[num_vc_data].opaque_data,
+ DHCP6_VCI_STRING, vci_strlen);
+ num_vc_data++;
+
+ opt_len = sizeof(struct dhcp6_option_vendor_class) +
+ sizeof(struct vendor_class_data) * num_vc_data +
+ vci_strlen;
+ break;
+ case DHCP6_OPTION_NII:
+ dhcp_option_start[0] = 1;
+ dhcp_option_start[1] = 0;
+ dhcp_option_start[2] = 0;
+
+ opt_len = 3;
+ break;
+ default:
+ printf("***Warning unknown DHCP6 option %d. Not adding to message\n", option_id);
+ return 0;
+ }
+ dhcp_option->option_len = htons(opt_len);
+
+ return opt_len + sizeof(struct dhcp6_option_hdr);
+}
+
+/**
+ * dhcp6_send_solicit_packet() - Send a SOLICIT packet
+ *
+ * Implements RFC 8415:
+ * - 16.2. Solicit Message
+ * - 18.2.1. Creation and Transmission of Solicit Messages
+ *
+ * Adds DHCP6 header and DHCP6 options. Sends the UDP packet
+ * and sets the UDP handler.
+ */
+static void dhcp6_send_solicit_packet(void)
+{
+ struct in6_addr dhcp_bcast_ip6;
+ int len = 0;
+ uchar *pkt;
+ uchar *dhcp_pkt_start_ptr;
+ struct dhcp6_hdr *dhcp_hdr;
+
+ pkt = net_tx_packet + net_eth_hdr_size() + IP6_HDR_SIZE + UDP_HDR_SIZE;
+ dhcp_pkt_start_ptr = pkt;
+
+ /* Add the DHCP6 header */
+ dhcp_hdr = (struct dhcp6_hdr *)pkt;
+ dhcp_hdr->msg_type = DHCP6_MSG_SOLICIT;
+ dhcp_hdr->trans_id = htons(sm_params.trans_id);
+ pkt += sizeof(struct dhcp6_hdr);
+
+ /* Add the options */
+ pkt += dhcp6_add_option(DHCP6_OPTION_CLIENTID, pkt);
+ pkt += dhcp6_add_option(DHCP6_OPTION_ELAPSED_TIME, pkt);
+ pkt += dhcp6_add_option(DHCP6_OPTION_IA_NA, pkt);
+ pkt += dhcp6_add_option(DHCP6_OPTION_ORO, pkt);
+ if (CONFIG_DHCP6_PXE_CLIENTARCH != 0xFF)
+ pkt += dhcp6_add_option(DHCP6_OPTION_CLIENT_ARCH_TYPE, pkt);
+ pkt += dhcp6_add_option(DHCP6_OPTION_VENDOR_CLASS, pkt);
+ pkt += dhcp6_add_option(DHCP6_OPTION_NII, pkt);
+
+ /* calculate packet length */
+ len = pkt - dhcp_pkt_start_ptr;
+
+ /* send UDP packet to DHCP6 multicast address */
+ string_to_ip6(DHCP6_MULTICAST_ADDR, sizeof(DHCP6_MULTICAST_ADDR), &dhcp_bcast_ip6);
+ net_set_udp_handler(dhcp6_handler);
+ net_send_udp_packet6((uchar *)net_bcast_ethaddr, &dhcp_bcast_ip6,
+ PORT_DHCP6_S, PORT_DHCP6_C, len);
+}
+
+/**
+ * dhcp6_send_request_packet() - Send a REQUEST packet
+ *
+ * * Implements RFC 8415:
+ * - 16.4. Request Message
+ * - 18.2.2. Creation and Transmission of Request Messages
+ *
+ * Adds DHCP6 header and DHCP6 options. Sends the UDP packet
+ * and sets the UDP handler.
+ */
+static void dhcp6_send_request_packet(void)
+{
+ struct in6_addr dhcp_bcast_ip6;
+ int len = 0;
+ uchar *pkt;
+ uchar *dhcp_pkt_start_ptr;
+ struct dhcp6_hdr *dhcp_hdr;
+
+ pkt = net_tx_packet + net_eth_hdr_size() + IP6_HDR_SIZE + UDP_HDR_SIZE;
+ dhcp_pkt_start_ptr = pkt;
+
+ /* Add the DHCP6 header */
+ dhcp_hdr = (struct dhcp6_hdr *)pkt;
+ dhcp_hdr->msg_type = DHCP6_MSG_REQUEST;
+ dhcp_hdr->trans_id = htons(sm_params.trans_id);
+ pkt += sizeof(struct dhcp6_hdr);
+
+ /* add the options */
+ pkt += dhcp6_add_option(DHCP6_OPTION_CLIENTID, pkt);
+ pkt += dhcp6_add_option(DHCP6_OPTION_ELAPSED_TIME, pkt);
+ pkt += dhcp6_add_option(DHCP6_OPTION_IA_NA, pkt);
+ pkt += dhcp6_add_option(DHCP6_OPTION_ORO, pkt);
+ /* copy received IA_TA/IA_NA into the REQUEST packet */
+ if (sm_params.server_uid.uid_ptr) {
+ memcpy(pkt, sm_params.server_uid.uid_ptr, sm_params.server_uid.uid_size);
+ pkt += sm_params.server_uid.uid_size;
+ }
+ if (CONFIG_DHCP6_PXE_CLIENTARCH != 0xFF)
+ pkt += dhcp6_add_option(DHCP6_OPTION_CLIENT_ARCH_TYPE, pkt);
+ pkt += dhcp6_add_option(DHCP6_OPTION_VENDOR_CLASS, pkt);
+ pkt += dhcp6_add_option(DHCP6_OPTION_NII, pkt);
+
+ /* calculate packet length */
+ len = pkt - dhcp_pkt_start_ptr;
+
+ /* send UDP packet to DHCP6 multicast address */
+ string_to_ip6(DHCP6_MULTICAST_ADDR, strlen(DHCP6_MULTICAST_ADDR), &dhcp_bcast_ip6);
+ net_set_udp_handler(dhcp6_handler);
+ net_send_udp_packet6((uchar *)net_bcast_ethaddr, &dhcp_bcast_ip6,
+ PORT_DHCP6_S, PORT_DHCP6_C, len);
+}
+
+static void dhcp6_parse_ia_options(struct dhcp6_option_hdr *ia_ptr, uchar *ia_option_ptr)
+{
+ struct dhcp6_option_hdr *ia_option_hdr;
+
+ ia_option_hdr = (struct dhcp6_option_hdr *)ia_option_ptr;
+
+ /* Search for options encapsulated in IA_NA/IA_TA (DHCP6_OPTION_IAADDR
+ * or DHCP6_OPTION_STATUS_CODE)
+ */
+ while (ia_option_ptr < ((uchar *)ia_ptr + ntohs(ia_ptr->option_len))) {
+ switch (ntohs(ia_option_hdr->option_id)) {
+ case DHCP6_OPTION_IAADDR:
+ sm_params.rx_status.ia_addr_found = true;
+ net_copy_ip6(&sm_params.rx_status.ia_addr_ipv6,
+ (ia_option_ptr + sizeof(struct dhcp6_hdr)));
+ debug("DHCP6_OPTION_IAADDR FOUND\n");
+ break;
+ case DHCP6_OPTION_STATUS_CODE:
+ sm_params.rx_status.ia_status_code =
+ ntohs(*((u16 *)(ia_option_ptr + sizeof(struct dhcp6_hdr))));
+ printf("ERROR : IA STATUS %d\n", sm_params.rx_status.ia_status_code);
+ break;
+ default:
+ debug("Unknown Option in IA, skipping\n");
+ break;
+ }
+
+ ia_option_ptr += ntohs(((struct dhcp6_option_hdr *)ia_option_ptr)->option_len);
+ }
+}
+
+/**
+ * dhcp6_parse_options() - Parse the DHCP6 options
+ *
+ * @rx_pkt: pointer to beginning of received DHCP6 packet
+ * @len: Total length of the DHCP6 packet
+ *
+ * Parses the DHCP options from a received DHCP packet. Perform error checking
+ * on the options received. Any relevant status is available in:
+ * "sm_params.rx_status"
+ *
+ */
+static void dhcp6_parse_options(uchar *rx_pkt, unsigned int len)
+{
+ uchar *option_ptr;
+ int sol_max_rt_sec, option_len, param_len_1;
+ char *s, *e;
+ struct dhcp6_option_hdr *option_hdr;
+
+ memset(&sm_params.rx_status, 0, sizeof(struct dhcp6_rx_pkt_status));
+
+ option_hdr = (struct dhcp6_option_hdr *)(rx_pkt + sizeof(struct dhcp6_hdr));
+ /* check that required options exist */
+ while (option_hdr < (struct dhcp6_option_hdr *)(rx_pkt + len)) {
+ option_ptr = ((uchar *)option_hdr) + sizeof(struct dhcp6_hdr);
+ option_len = ntohs(option_hdr->option_len);
+
+ if (option_ptr + option_len > rx_pkt + len) {
+ debug("Invalid option length\n");
+ return;
+ }
+
+ switch (ntohs(option_hdr->option_id)) {
+ case DHCP6_OPTION_CLIENTID:
+ if (memcmp(option_ptr, sm_params.duid, option_len)
+ != 0) {
+ debug("CLIENT ID DOESN'T MATCH\n");
+ } else {
+ debug("CLIENT ID FOUND and MATCHES\n");
+ sm_params.rx_status.client_id_match = true;
+ }
+ break;
+ case DHCP6_OPTION_SERVERID:
+ sm_params.rx_status.server_id_found = true;
+ sm_params.rx_status.server_uid_ptr = (uchar *)option_hdr;
+ sm_params.rx_status.server_uid_size = option_len +
+ sizeof(struct dhcp6_option_hdr);
+ debug("SERVER ID FOUND\n");
+ break;
+ case DHCP6_OPTION_IA_TA:
+ case DHCP6_OPTION_IA_NA:
+ /* check the IA_ID */
+ if (*((u32 *)option_ptr) != htonl(sm_params.ia_id)) {
+ debug("IA_ID mismatch 0x%08x 0x%08x\n",
+ *((u32 *)option_ptr), htonl(sm_params.ia_id));
+ break;
+ }
+
+ if (ntohs(option_hdr->option_id) == DHCP6_OPTION_IA_NA) {
+ /* skip past IA_ID/T1/T2 */
+ option_ptr += 3 * sizeof(u32);
+ } else if (ntohs(option_hdr->option_id) == DHCP6_OPTION_IA_TA) {
+ /* skip past IA_ID */
+ option_ptr += sizeof(u32);
+ }
+ /* parse the IA_NA/IA_TA encapsulated options */
+ dhcp6_parse_ia_options(option_hdr, option_ptr);
+ break;
+ case DHCP6_OPTION_STATUS_CODE:
+ debug("DHCP6_OPTION_STATUS_CODE FOUND\n");
+ sm_params.rx_status.status_code = ntohs(*((u16 *)option_ptr));
+ debug("DHCP6 top-level status code %d\n", sm_params.rx_status.status_code);
+ debug("DHCP6 status message: %.*s\n", len, option_ptr + 2);
+ break;
+ case DHCP6_OPTION_SOL_MAX_RT:
+ debug("DHCP6_OPTION_SOL_MAX_RT FOUND\n");
+ sol_max_rt_sec = ntohl(*((u32 *)option_ptr));
+
+ /* A DHCP client MUST ignore any SOL_MAX_RT option values that are less
+ * than 60 or more than 86400
+ */
+ if (sol_max_rt_sec >= 60 && sol_max_rt_sec <= 86400) {
+ updated_sol_max_rt_ms = sol_max_rt_sec * 1000;
+ if (sm_params.curr_state == DHCP6_SOLICIT)
+ sm_params.mrt_ms = updated_sol_max_rt_ms;
+ }
+ break;
+ case DHCP6_OPTION_OPT_BOOTFILE_URL:
+ debug("DHCP6_OPTION_OPT_BOOTFILE_URL FOUND\n");
+ copy_filename(net_boot_file_name, option_ptr, option_len + 1);
+ debug("net_boot_file_name: %s\n", net_boot_file_name);
+
+ /* copy server_ip6 (required for PXE) */
+ s = strchr(net_boot_file_name, '[');
+ e = strchr(net_boot_file_name, ']');
+ if (s && e && e > s)
+ string_to_ip6(s + 1, e - s - 1, &net_server_ip6);
+ break;
+ case DHCP6_OPTION_OPT_BOOTFILE_PARAM:
+ if (IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION)) {
+ debug("DHCP6_OPTION_OPT_BOOTFILE_PARAM FOUND\n");
+ /* if CONFIG_DHCP6_PXE_DHCP_OPTION is set the PXE config file path
+ * is contained in the first OPT_BOOTFILE_PARAM argument
+ */
+ param_len_1 = ntohs(*((u16 *)option_ptr));
+ option_ptr += sizeof(u16);
+ if (param_len_1 + sizeof(u16) > option_len) {
+ debug("Invalid BOOTFILE_PARAM param_len_1. Skipping\n");
+ break;
+ }
+
+ if (pxelinux_configfile)
+ free(pxelinux_configfile);
+
+ pxelinux_configfile = (char *)malloc((param_len_1 + 1) *
+ sizeof(char));
+ if (pxelinux_configfile)
+ strlcpy(pxelinux_configfile, option_ptr, param_len_1 + 1);
+ else
+ printf("Error: Failed to allocate pxelinux_configfile\n");
+
+ debug("PXE CONFIG FILE %s\n", pxelinux_configfile);
+ }
+ break;
+ case DHCP6_OPTION_PREFERENCE:
+ debug("DHCP6_OPTION_PREFERENCE FOUND\n");
+ sm_params.rx_status.preference = *option_ptr;
+ break;
+ default:
+ debug("Unknown Option ID: %d, skipping parsing\n",
+ ntohs(option_hdr->option_id));
+ break;
+ }
+ /* Increment to next option header */
+ option_hdr = (struct dhcp6_option_hdr *)(((uchar *)option_hdr) +
+ sizeof(struct dhcp6_option_hdr) + option_len);
+ }
+}
+
+/**
+ * dhcp6_check_advertise_packet() - Perform error checking on an expected
+ * ADVERTISE packet.
+ *
+ * @rx_pkt: pointer to beginning of received DHCP6 packet
+ * @len: Total length of the DHCP6 packet
+ *
+ * Implements RFC 8415:
+ * - 16.3. Advertise Message
+ * - 18.2.10. Receipt of Reply Messages
+ *
+ * Return : 0 : ADVERTISE packet was received with no errors.
+ * State machine can progress
+ * 1 : - packet received is not an ADVERTISE packet
+ * - there were errors in the packet received,
+ * - this is the first SOLICIT packet, but
+ * received preference is not 255, so we have
+ * to wait for more server responses.
+ */
+static int dhcp6_check_advertise_packet(uchar *rx_pkt, unsigned int len)
+{
+ u16 rx_uid_size;
+ struct dhcp6_hdr *dhcp6_hdr = (struct dhcp6_hdr *)rx_pkt;
+
+ /* Ignore message if msg-type != advertise */
+ if (dhcp6_hdr->msg_type != DHCP6_MSG_ADVERTISE)
+ return 1;
+ /* Ignore message if transaction ID doesn't match */
+ if (dhcp6_hdr->trans_id != htons(sm_params.trans_id))
+ return 1;
+
+ dhcp6_parse_options(rx_pkt, len);
+
+ /* Ignore advertise if any of these conditions met */
+ if (!sm_params.rx_status.server_id_found ||
+ !sm_params.rx_status.client_id_match ||
+ sm_params.rx_status.status_code != DHCP6_SUCCESS) {
+ return 1;
+ }
+
+ if (sm_params.rx_status.server_id_found) {
+ /* if no server UID has been received yet, or if the server UID
+ * received has a higher preference value than the currently saved
+ * server UID, save the new server UID and preference
+ */
+ if (!sm_params.server_uid.uid_ptr ||
+ (sm_params.server_uid.uid_ptr &&
+ sm_params.server_uid.preference < sm_params.rx_status.preference)) {
+ rx_uid_size = sm_params.rx_status.server_uid_size;
+ if (sm_params.server_uid.uid_ptr)
+ free(sm_params.server_uid.uid_ptr);
+ sm_params.server_uid.uid_ptr = malloc(rx_uid_size * sizeof(uchar));
+ if (sm_params.server_uid.uid_ptr)
+ memcpy(sm_params.server_uid.uid_ptr,
+ sm_params.rx_status.server_uid_ptr, rx_uid_size);
+
+ sm_params.server_uid.uid_size = rx_uid_size;
+ sm_params.server_uid.preference = sm_params.rx_status.preference;
+ }
+
+ /* If the first SOLICIT and preference code is 255, use right away.
+ * Otherwise, wait for the first SOLICIT period for more
+ * DHCP6 servers to respond.
+ */
+ if (sm_params.retry_cnt == 1 &&
+ sm_params.server_uid.preference != 255) {
+ debug("valid ADVERTISE, waiting for first SOLICIT period\n");
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * dhcp6_check_reply_packet() - Perform error checking on an expected
+ * REPLY packet.
+ *
+ * @rx_pkt: pointer to beginning of received DHCP6 packet
+ * @len: Total length of the DHCP6 packet
+ *
+ * Implements RFC 8415:
+ * - 16.10. Reply Message
+ * - 18.2.10. Receipt of Reply Messages
+ *
+ * Return : 0 - REPLY packet was received with no errors
+ * 1 - packet received is not an REPLY packet,
+ * or there were errors in the packet received
+ */
+static int dhcp6_check_reply_packet(uchar *rx_pkt, unsigned int len)
+{
+ struct dhcp6_hdr *dhcp6_hdr = (struct dhcp6_hdr *)rx_pkt;
+
+ /* Ignore message if msg-type != reply */
+ if (dhcp6_hdr->msg_type != DHCP6_MSG_REPLY)
+ return 1;
+ /* check that transaction ID matches */
+ if (dhcp6_hdr->trans_id != htons(sm_params.trans_id))
+ return 1;
+
+ dhcp6_parse_options(rx_pkt, len);
+
+ /* if no addresses found, restart DHCP */
+ if (!sm_params.rx_status.ia_addr_found ||
+ sm_params.rx_status.ia_status_code == DHCP6_NO_ADDRS_AVAIL ||
+ sm_params.rx_status.status_code == DHCP6_NOT_ON_LINK) {
+ /* restart DHCP */
+ debug("No address found in reply. Restarting DHCP\n");
+ dhcp6_start();
+ }
+
+ /* ignore reply if any of these conditions met */
+ if (!sm_params.rx_status.server_id_found ||
+ !sm_params.rx_status.client_id_match ||
+ sm_params.rx_status.status_code == DHCP6_UNSPEC_FAIL) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Timeout for DHCP6 SOLICIT/REQUEST */
+static void dhcp6_timeout_handler(void)
+{
+ /* call state machine with the timeout flag */
+ dhcp6_state_machine(true, NULL, 0);
+}
+
+/**
+ * dhcp6_state_machine() - DHCP6 state machine
+ *
+ * @timeout: TRUE : timeout waiting for response from
+ * DHCP6 server
+ * FALSE : init or received response from DHCP6 server
+ * @rx_pkt: Pointer to the beginning of received DHCP6 packet.
+ * Will be NULL if called as part of init
+ * or timeout==TRUE
+ * @len: Total length of the DHCP6 packet if rx_pkt != NULL
+ *
+ * Implements RFC 8415:
+ * - 5.2. Client/Server Exchanges Involving Four Messages
+ * - 15. Reliability of Client-Initiated Message Exchanges
+ *
+ * Handles:
+ * - transmission of SOLICIT and REQUEST packets
+ * - retransmission of SOLICIT and REQUEST packets if no
+ * response is received within the timeout window
+ * - checking received ADVERTISE and REPLY packets to
+ * assess if the DHCP state machine can progress
+ */
+static void dhcp6_state_machine(bool timeout, uchar *rx_pkt, unsigned int len)
+{
+ int rand_minus_plus_100;
+
+ switch (sm_params.curr_state) {
+ case DHCP6_INIT:
+ sm_params.next_state = DHCP6_SOLICIT;
+ break;
+ case DHCP6_SOLICIT:
+ if (!timeout) {
+ /* check the rx packet and determine if we can transition to next
+ * state.
+ */
+ if (dhcp6_check_advertise_packet(rx_pkt, len))
+ return;
+
+ debug("ADVERTISE good, transition to REQUEST\n");
+ sm_params.next_state = DHCP6_REQUEST;
+ } else if (sm_params.retry_cnt == 1) {
+ /* If a server UID was received in the first SOLICIT period
+ * transition to REQUEST
+ */
+ if (sm_params.server_uid.uid_ptr)
+ sm_params.next_state = DHCP6_REQUEST;
+ }
+ break;
+ case DHCP6_REQUEST:
+ if (!timeout) {
+ /* check the rx packet and determine if we can transition to next state */
+ if (dhcp6_check_reply_packet(rx_pkt, len))
+ return;
+
+ debug("REPLY good, transition to DONE\n");
+ sm_params.next_state = DHCP6_DONE;
+ }
+ break;
+ case DHCP6_DONE:
+ case DHCP6_FAIL:
+ /* Shouldn't get here, as state machine should exit
+ * immediately when DHCP6_DONE or DHCP6_FAIL is entered.
+ * Proceed anyway to proceed DONE/FAIL actions
+ */
+ debug("Unexpected DHCP6 state : %d\n", sm_params.curr_state);
+ break;
+ }
+ /* re-seed the RNG */
+ srand(get_ticks() + rand());
+
+ /* handle state machine entry conditions */
+ if (sm_params.curr_state != sm_params.next_state) {
+ sm_params.retry_cnt = 0;
+
+ if (sm_params.next_state == DHCP6_SOLICIT) {
+ /* delay a random ammount (special for SOLICIT) */
+ udelay((rand() % SOL_MAX_DELAY_MS) * 1000);
+ /* init timestamp variables after SOLICIT delay */
+ sm_params.dhcp6_start_ms = get_timer(0);
+ sm_params.dhcp6_retry_start_ms = sm_params.dhcp6_start_ms;
+ sm_params.dhcp6_retry_ms = sm_params.dhcp6_start_ms;
+ /* init transaction and ia_id */
+ sm_params.trans_id = rand() & 0xFFFFFF;
+ sm_params.ia_id = rand();
+ /* initialize retransmission parameters */
+ sm_params.irt_ms = SOL_TIMEOUT_MS;
+ sm_params.mrt_ms = updated_sol_max_rt_ms;
+ /* RFCs default MRC is be 0 (try infinitely)
+ * give up after CONFIG_NET_RETRY_COUNT number of tries (same as DHCPv4)
+ */
+ sm_params.mrc = CONFIG_NET_RETRY_COUNT;
+ sm_params.mrd_ms = 0;
+
+ } else if (sm_params.next_state == DHCP6_REQUEST) {
+ /* init timestamp variables */
+ sm_params.dhcp6_retry_start_ms = get_timer(0);
+ sm_params.dhcp6_retry_ms = sm_params.dhcp6_start_ms;
+ /* initialize retransmission parameters */
+ sm_params.irt_ms = REQ_TIMEOUT_MS;
+ sm_params.mrt_ms = REQ_MAX_RT_MS;
+ sm_params.mrc = REQ_MAX_RC;
+ sm_params.mrd_ms = 0;
+ }
+ }
+
+ if (timeout)
+ sm_params.dhcp6_retry_ms = get_timer(0);
+
+ /* Check if MRC or MRD have been passed */
+ if ((sm_params.mrc != 0 &&
+ sm_params.retry_cnt >= sm_params.mrc) ||
+ (sm_params.mrd_ms != 0 &&
+ ((sm_params.dhcp6_retry_ms - sm_params.dhcp6_retry_start_ms) >= sm_params.mrd_ms))) {
+ sm_params.next_state = DHCP6_FAIL;
+ }
+
+ /* calculate retransmission timeout (RT) */
+ rand_minus_plus_100 = ((rand() % 200) - 100);
+ if (sm_params.retry_cnt == 0) {
+ sm_params.rt_ms = sm_params.irt_ms +
+ ((sm_params.irt_ms * rand_minus_plus_100) / 1000);
+ } else {
+ sm_params.rt_ms = (2 * sm_params.rt_prev_ms) +
+ ((sm_params.rt_prev_ms * rand_minus_plus_100) / 1000);
+ }
+
+ if (sm_params.rt_ms > sm_params.mrt_ms) {
+ sm_params.rt_ms = sm_params.mrt_ms +
+ ((sm_params.mrt_ms * rand_minus_plus_100) / 1000);
+ }
+
+ sm_params.rt_prev_ms = sm_params.rt_ms;
+
+ net_set_timeout_handler(sm_params.rt_ms, dhcp6_timeout_handler);
+
+ /* send transmit/retransmit message or fail */
+ sm_params.curr_state = sm_params.next_state;
+
+ if (sm_params.curr_state == DHCP6_SOLICIT) {
+ /* send solicit packet */
+ dhcp6_send_solicit_packet();
+ printf("DHCP6 SOLICIT %d\n", sm_params.retry_cnt);
+ } else if (sm_params.curr_state == DHCP6_REQUEST) {
+ /* send request packet */
+ dhcp6_send_request_packet();
+ printf("DHCP6 REQUEST %d\n", sm_params.retry_cnt);
+ } else if (sm_params.curr_state == DHCP6_DONE) {
+ net_set_timeout_handler(0, NULL);
+
+ /* Duplicate address detection (DAD) should be
+ * performed here before setting net_ip6
+ * (enhancement should be considered)
+ */
+ net_copy_ip6(&net_ip6, &sm_params.rx_status.ia_addr_ipv6);
+ printf("DHCP6 client bound to %pI6c\n", &net_ip6);
+ /* will load with TFTP6 */
+ net_auto_load();
+ } else if (sm_params.curr_state == DHCP6_FAIL) {
+ printf("DHCP6 FAILED, TERMINATING\n");
+ net_set_state(NETLOOP_FAIL);
+ }
+ sm_params.retry_cnt++;
+}
+
+/* Start or restart DHCP6 */
+void dhcp6_start(void)
+{
+ memset(&sm_params, 0, sizeof(struct dhcp6_sm_params));
+
+ /* seed the RNG with MAC address */
+ srand_mac();
+
+ sm_params.curr_state = DHCP6_INIT;
+ dhcp6_state_machine(false, NULL, 0);
+}
diff --git a/net/dhcpv6.h b/net/dhcpv6.h
new file mode 100644
index 00000000000..65c8e4c71d3
--- /dev/null
+++ b/net/dhcpv6.h
@@ -0,0 +1,256 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) Microsoft Corporation
+ * Author: Sean Edmond <seanedmond@microsoft.com>
+ *
+ */
+
+#ifndef __DHCP6_H__
+#define __DHCP6_H__
+
+/* Message types */
+#define DHCP6_MSG_SOLICIT 1
+#define DHCP6_MSG_ADVERTISE 2
+#define DHCP6_MSG_REQUEST 3
+#define DHCP6_MSG_REPLY 7
+
+/* Option Codes */
+#define DHCP6_OPTION_CLIENTID 1
+#define DHCP6_OPTION_SERVERID 2
+#define DHCP6_OPTION_IA_NA 3
+#define DHCP6_OPTION_IA_TA 4
+#define DHCP6_OPTION_IAADDR 5
+#define DHCP6_OPTION_ORO 6
+#define DHCP6_OPTION_PREFERENCE 7
+#define DHCP6_OPTION_ELAPSED_TIME 8
+#define DHCP6_OPTION_STATUS_CODE 13
+#define DHCP6_OPTION_OPT_BOOTFILE_URL 59
+#define DHCP6_OPTION_OPT_BOOTFILE_PARAM 60
+#define DHCP6_OPTION_SOL_MAX_RT 82
+#define DHCP6_OPTION_CLIENT_ARCH_TYPE 61
+#define DHCP6_OPTION_VENDOR_CLASS 16
+#define DHCP6_OPTION_NII 62
+
+/* DUID */
+#define DUID_TYPE_LL 3
+#define DUID_HW_TYPE_ENET 1
+#define DUID_LL_SIZE (sizeof(struct dhcp6_option_duid_ll) + ETH_ALEN)
+#define DUID_MAX_SIZE DUID_LL_SIZE /* only supports DUID-LL currently */
+
+/* vendor-class-data to send in vendor clas option */
+#define DHCP6_VCI_STRING "U-Boot"
+
+#define DHCP6_MULTICAST_ADDR "ff02::1:2" /* DHCP multicast address */
+
+/* DHCP6 States supported */
+enum dhcp6_state {
+ DHCP6_INIT,
+ DHCP6_SOLICIT,
+ DHCP6_REQUEST,
+ DHCP6_DONE,
+ DHCP6_FAIL,
+};
+
+/* DHCP6 Status codes */
+enum dhcp6_status {
+ DHCP6_SUCCESS = 0,
+ DHCP6_UNSPEC_FAIL = 1,
+ DHCP6_NO_ADDRS_AVAIL = 2,
+ DHCP6_NO_BINDING = 3,
+ DHCP6_NOT_ON_LINK = 4,
+ DHCP6_USE_MULTICAST = 5,
+ DHCP6_NO_PREFIX_AVAIL = 6,
+};
+
+/* DHCP6 message header format */
+struct dhcp6_hdr {
+ unsigned int msg_type : 8; /* message type */
+ unsigned int trans_id : 24; /* transaction ID */
+} __packed;
+
+/* DHCP6 option header format */
+struct dhcp6_option_hdr {
+ __be16 option_id; /* option id */
+ __be16 option_len; /* Option length */
+ u8 option_data[0]; /* Option data */
+} __packed;
+
+/* DHCP6_OPTION_CLIENTID option (DUID-LL) */
+struct dhcp6_option_duid_ll {
+ __be16 duid_type;
+ __be16 hw_type;
+ u8 ll_addr[0];
+} __packed;
+
+/* DHCP6_OPTION_ELAPSED_TIME option */
+struct dhcp6_option_elapsed_time {
+ __be16 elapsed_time;
+} __packed;
+
+/* DHCP6_OPTION_IA_TA option */
+struct dhcp6_option_ia_ta {
+ __be32 iaid;
+ u8 ia_ta_options[0];
+} __packed;
+
+/* DHCP6_OPTION_IA_NA option */
+struct dhcp6_option_ia_na {
+ __be32 iaid;
+ __be32 t1;
+ __be32 t2;
+ u8 ia_na_options[0];
+} __packed;
+
+/* OPTION_ORO option */
+struct dhcp6_option_oro {
+ __be16 req_option_code[0];
+} __packed;
+
+/* DHCP6_OPTION_CLIENT_ARCH_TYPE option */
+struct dhcp6_option_client_arch {
+ __be16 arch_type[0];
+} __packed;
+
+/* vendor-class-data inside OPTION_VENDOR_CLASS option */
+struct vendor_class_data {
+ __be16 vendor_class_len;
+ u8 opaque_data[0];
+} __packed;
+
+/* DHCP6_OPTION_VENDOR_CLASS option */
+struct dhcp6_option_vendor_class {
+ __be32 enterprise_number;
+ struct vendor_class_data vendor_class_data[0];
+} __packed;
+
+/**
+ * struct dhcp6_rx_pkt_status - Structure that holds status
+ * from a received message
+ * @client_id_match: Client ID was found and matches DUID sent
+ * @server_id_found: Server ID was found in the message
+ * @server_uid_ptr: Pointer to received server ID
+ * @server_uid_size: Size of received server ID
+ * @ia_addr_found: IA addr option was found in received message
+ * @ia_addr_ipv6: The IPv6 address received in IA
+ * @ia_status_code: Status code received in the IA
+ * @status_code: Top-level status code received
+ * @preference: Preference code received
+ */
+struct dhcp6_rx_pkt_status {
+ bool client_id_match;
+ bool server_id_found;
+ uchar *server_uid_ptr;
+ u16 server_uid_size;
+ bool ia_addr_found;
+ struct in6_addr ia_addr_ipv6;
+ enum dhcp6_status ia_status_code;
+ enum dhcp6_status status_code;
+ u8 preference;
+};
+
+/**
+ * struct dhcp6_server_uid - Structure that holds the server UID
+ * received from an ADVERTISE and saved
+ * given the server selection criteria.
+ * @uid_ptr: Dynamically allocated and copied server UID
+ * @uid_size: Size of the server UID in uid_ptr (in bytes)
+ * @preference: Preference code associated with this server UID
+ */
+struct dhcp6_server_uid {
+ uchar *uid_ptr;
+ u16 uid_size;
+ u8 preference;
+};
+
+/**
+ * struct dhcp6_sm_params - Structure that holds DHCP6
+ * state machine parameters
+ * @curr_state: current DHCP6 state
+ * @next_state: next DHCP6 state
+ * @dhcp6_start_ms: timestamp DHCP6 start
+ * @dhcp6_retry_start_ms: timestamp of current TX message start
+ * @dhcp6_retry_ms: timestamp of last retransmission
+ * @retry_cnt: retry count
+ * @trans_id: transaction ID
+ * @ia_id: transmitted IA ID
+ * @irt_ms: Initial retransmission time (in ms)
+ * @mrt_ms: Maximum retransmission time (in ms)
+ * @mrc: Maximum retransmission count
+ * @mrd_ms: Maximum retransmission duration (in ms)
+ * @rt_ms: retransmission timeout (is ms)
+ * @rt_prev_ms: previous retransmission timeout
+ * @rx_status: Status from received message
+ * @server_uid: Saved Server UID for selected server
+ * @duid: pointer to transmitted Client DUID
+ */
+struct dhcp6_sm_params {
+ enum dhcp6_state curr_state;
+ enum dhcp6_state next_state;
+ ulong dhcp6_start_ms;
+ ulong dhcp6_retry_start_ms;
+ ulong dhcp6_retry_ms;
+ u32 retry_cnt;
+ u32 trans_id;
+ u32 ia_id;
+ int irt_ms;
+ int mrt_ms;
+ int mrc;
+ int mrd_ms;
+ int rt_ms;
+ int rt_prev_ms;
+ struct dhcp6_rx_pkt_status rx_status;
+ struct dhcp6_server_uid server_uid;
+ char duid[DUID_MAX_SIZE];
+};
+
+/* Starts a DHCPv6 4-message exchange as a DHCPv6 client. On successful exchange,
+ * the DHCPv6 state machine will transition from internal states:
+ * DHCP6_INIT->DHCP6_SOLICIT->DHCP6_REQUEST->DHCP6_DONE
+ *
+ * Transmitted SOLICIT and REQUEST packets will set/request the minimum required
+ * DHCPv6 options to PXE boot.
+ *
+ * After a successful exchange, the DHCPv6 assigned address will be set in net_ip6
+ *
+ * Additionally, the following will be set after receiving these options:
+ * DHCP6_OPTION_OPT_BOOTFILE_URL (option 59) -> net_server_ip6, net_boot_file_name
+ * DHCP6_OPTION_OPT_BOOTFILE_PARAM (option 60) - > pxelinux_configfile
+ *
+ * Illustration of a 4-message exchange with 2 servers (copied from
+ * https://www.rfc-editor.org/rfc/rfc8415):
+ *
+ * Server Server
+ * (not selected) Client (selected)
+ *
+ * v v v
+ * | | |
+ * | Begins initialization |
+ * | | |
+ * start of | _____________/|\_____________ |
+ * 4-message |/ Solicit | Solicit \|
+ * exchange | | |
+ * Determines | Determines
+ * configuration | configuration
+ * | | |
+ * |\ | ____________/|
+ * | \________ | /Advertise |
+ * | Advertise\ |/ |
+ * | \ | |
+ * | Collects Advertises |
+ * | \ | |
+ * | Selects configuration |
+ * | | |
+ * | _____________/|\_____________ |
+ * |/ Request | Request \|
+ * | | |
+ * | | Commits configuration
+ * | | |
+ * end of | | _____________/|
+ * 4-message | |/ Reply |
+ * exchange | | |
+ * | Initialization complete |
+ * | | |
+ */
+void dhcp6_start(void);
+
+#endif /* __DHCP6_H__ */
diff --git a/net/dns.c b/net/dns.c
new file mode 100644
index 00000000000..08ad54a04d5
--- /dev/null
+++ b/net/dns.c
@@ -0,0 +1,217 @@
+/*
+ * DNS support driver
+ *
+ * Copyright (c) 2008 Pieter Voorthuijsen <pieter.voorthuijsen@prodrive.nl>
+ * Copyright (c) 2009 Robin Getz <rgetz@blackfin.uclinux.org>
+ *
+ * This is a simple DNS implementation for U-Boot. It will use the first IP
+ * in the DNS response as net_server_ip. This can then be used for any other
+ * network related activities.
+ *
+ * The packet handling is partly based on TADNS, original copyrights
+ * follow below.
+ *
+ */
+
+/*
+ * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
+ *
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * Sergey Lyubka wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return.
+ */
+
+#include <command.h>
+#include <env.h>
+#include <log.h>
+#include <net.h>
+#include <asm/unaligned.h>
+
+#include "dns.h"
+
+char *net_dns_resolve; /* The host to resolve */
+char *net_dns_env_var; /* The envvar to store the answer in */
+
+static int dns_our_port;
+
+/*
+ * make port a little random (1024-17407)
+ * This keeps the math somewhat trivial to compute, and seems to work with
+ * all supported protocols/clients/servers
+ */
+static unsigned int random_port(void)
+{
+ return 1024 + (get_timer(0) % 0x4000);
+}
+
+static void dns_send(void)
+{
+ struct header *header;
+ int n, name_len;
+ uchar *p, *pkt;
+ const char *s;
+ const char *name;
+ enum dns_query_type qtype = DNS_A_RECORD;
+
+ name = net_dns_resolve;
+ pkt = (uchar *)(net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE);
+ p = pkt;
+
+ /* Prepare DNS packet header */
+ header = (struct header *)pkt;
+ header->tid = 1;
+ header->flags = htons(0x100); /* standard query */
+ header->nqueries = htons(1); /* Just one query */
+ header->nanswers = 0;
+ header->nauth = 0;
+ header->nother = 0;
+
+ /* Encode DNS name */
+ name_len = strlen(name);
+ p = (uchar *)&header->data; /* For encoding host name into packet */
+
+ do {
+ s = strchr(name, '.');
+ if (!s)
+ s = name + name_len;
+
+ n = s - name; /* Chunk length */
+ *p++ = n; /* Copy length */
+ memcpy(p, name, n); /* Copy chunk */
+ p += n;
+
+ if (*s == '.')
+ n++;
+
+ name += n;
+ name_len -= n;
+ } while (*s != '\0');
+
+ *p++ = 0; /* Mark end of host name */
+ *p++ = 0; /* Some servers require double null */
+ *p++ = (unsigned char) qtype; /* Query Type */
+
+ *p++ = 0;
+ *p++ = 1; /* Class: inet, 0x0001 */
+
+ n = p - pkt; /* Total packet length */
+ debug("Packet size %d\n", n);
+
+ dns_our_port = random_port();
+
+ net_send_udp_packet(net_server_ethaddr, net_dns_server,
+ DNS_SERVICE_PORT, dns_our_port, n);
+ debug("DNS packet sent\n");
+}
+
+static void dns_timeout_handler(void)
+{
+ puts("Timeout\n");
+ net_set_state(NETLOOP_FAIL);
+}
+
+static void dns_handler(uchar *pkt, unsigned dest, struct in_addr sip,
+ unsigned src, unsigned len)
+{
+ struct header *header;
+ const unsigned char *p, *e, *s;
+ u16 type, i;
+ int found, stop, dlen;
+ char ip_str[22];
+ struct in_addr ip_addr;
+
+ debug("%s\n", __func__);
+ if (dest != dns_our_port)
+ return;
+
+ for (i = 0; i < len; i += 4)
+ debug("0x%p - 0x%.2x 0x%.2x 0x%.2x 0x%.2x\n",
+ pkt+i, pkt[i], pkt[i+1], pkt[i+2], pkt[i+3]);
+
+ /* We sent one query. We want to have a single answer: */
+ header = (struct header *)pkt;
+ if (ntohs(header->nqueries) != 1)
+ return;
+
+ /* Received 0 answers */
+ if (header->nanswers == 0) {
+ puts("DNS: host not found\n");
+ net_set_state(NETLOOP_SUCCESS);
+ return;
+ }
+
+ /* Skip host name */
+ s = &header->data[0];
+ e = pkt + len;
+ for (p = s; p < e && *p != '\0'; p++)
+ continue;
+
+ /* We sent query class 1, query type 1 */
+ if (&p[5] > e || get_unaligned_be16(p+1) != DNS_A_RECORD) {
+ puts("DNS: response was not an A record\n");
+ net_set_state(NETLOOP_SUCCESS);
+ return;
+ }
+
+ /* Go to the first answer section */
+ p += 5;
+
+ /* Loop through the answers, we want A type answer */
+ for (found = stop = 0; !stop && &p[12] < e; ) {
+ /* Skip possible name in CNAME answer */
+ if (*p != 0xc0) {
+ while (*p && &p[12] < e)
+ p++;
+ p--;
+ }
+ debug("Name (Offset in header): %d\n", p[1]);
+
+ type = get_unaligned_be16(p+2);
+ debug("type = %d\n", type);
+ if (type == DNS_CNAME_RECORD) {
+ /* CNAME answer. shift to the next section */
+ debug("Found canonical name\n");
+ dlen = get_unaligned_be16(p+10);
+ debug("dlen = %d\n", dlen);
+ p += 12 + dlen;
+ } else if (type == DNS_A_RECORD) {
+ debug("Found A-record\n");
+ found = 1;
+ stop = 1;
+ } else {
+ debug("Unknown type\n");
+ stop = 1;
+ }
+ }
+
+ if (found && &p[12] < e) {
+ dlen = get_unaligned_be16(p+10);
+ p += 12;
+ memcpy(&ip_addr, p, 4);
+
+ if (p + dlen <= e) {
+ ip_to_string(ip_addr, ip_str);
+ printf("%s\n", ip_str);
+ if (net_dns_env_var)
+ env_set(net_dns_env_var, ip_str);
+ } else {
+ puts("server responded with invalid IP number\n");
+ }
+ }
+
+ net_set_state(NETLOOP_SUCCESS);
+}
+
+void dns_start(void)
+{
+ debug("%s\n", __func__);
+
+ net_set_timeout_handler(DNS_TIMEOUT, dns_timeout_handler);
+ net_set_udp_handler(dns_handler);
+
+ /* Clear a previous MAC address, the server IP might have changed. */
+ memset(net_server_ethaddr, 0, sizeof(net_server_ethaddr));
+
+ dns_send();
+}
diff --git a/net/dns.h b/net/dns.h
new file mode 100644
index 00000000000..79ac76f590d
--- /dev/null
+++ b/net/dns.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Masami Komiya <mkomiya@sonare.it> 2005
+ * Copyright 2009, Robin Getz <rgetz@blackfin.uclinux.org>
+ */
+
+#ifndef __DNS_H__
+#define __DNS_H__
+
+#define DNS_SERVICE_PORT 53
+#define DNS_TIMEOUT 10000UL
+
+/* http://en.wikipedia.org/wiki/List_of_DNS_record_types */
+enum dns_query_type {
+ DNS_A_RECORD = 0x01,
+ DNS_CNAME_RECORD = 0x05,
+ DNS_MX_RECORD = 0x0f,
+};
+
+/*
+ * DNS network packet
+ */
+struct header {
+ uint16_t tid; /* Transaction ID */
+ uint16_t flags; /* Flags */
+ uint16_t nqueries; /* Questions */
+ uint16_t nanswers; /* Answers */
+ uint16_t nauth; /* Authority PRs */
+ uint16_t nother; /* Other PRs */
+ unsigned char data[1]; /* Data, variable length */
+} __attribute__((packed));
+
+void dns_start(void); /* Begin DNS */
+
+#endif
diff --git a/net/dsa-uclass.c b/net/dsa-uclass.c
new file mode 100644
index 00000000000..f64c68e340d
--- /dev/null
+++ b/net/dsa-uclass.c
@@ -0,0 +1,533 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2019-2021 NXP
+ */
+
+#include <net/dsa.h>
+#include <dm/lists.h>
+#include <dm/device_compat.h>
+#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
+#include <linux/bitmap.h>
+#include <miiphy.h>
+
+#define DSA_PORT_CHILD_DRV_NAME "dsa-port"
+
+/* per-device internal state structure */
+struct dsa_priv {
+ struct phy_device *cpu_port_fixed_phy;
+ struct udevice *master_dev;
+ int num_ports;
+ u32 cpu_port;
+ int headroom;
+ int tailroom;
+};
+
+/* external API */
+int dsa_set_tagging(struct udevice *dev, ushort headroom, ushort tailroom)
+{
+ struct dsa_priv *priv;
+
+ if (!dev)
+ return -EINVAL;
+
+ if (headroom + tailroom > DSA_MAX_OVR)
+ return -EINVAL;
+
+ priv = dev_get_uclass_priv(dev);
+
+ if (headroom > 0)
+ priv->headroom = headroom;
+ if (tailroom > 0)
+ priv->tailroom = tailroom;
+
+ return 0;
+}
+
+ofnode dsa_port_get_ofnode(struct udevice *dev, int port)
+{
+ struct dsa_pdata *pdata = dev_get_uclass_plat(dev);
+ struct dsa_port_pdata *port_pdata;
+ struct udevice *pdev;
+
+ if (port == pdata->cpu_port)
+ return pdata->cpu_port_node;
+
+ for (device_find_first_child(dev, &pdev);
+ pdev;
+ device_find_next_child(&pdev)) {
+ port_pdata = dev_get_parent_plat(pdev);
+ if (port_pdata->index == port)
+ return dev_ofnode(pdev);
+ }
+
+ return ofnode_null();
+}
+
+/* returns the DSA master Ethernet device */
+struct udevice *dsa_get_master(struct udevice *dev)
+{
+ struct dsa_priv *priv;
+
+ if (!dev)
+ return NULL;
+
+ priv = dev_get_uclass_priv(dev);
+
+ return priv->master_dev;
+}
+
+/*
+ * Start the desired port, the CPU port and the master Eth interface.
+ * TODO: if cascaded we may need to _start ports in other switches too
+ */
+static int dsa_port_start(struct udevice *pdev)
+{
+ struct udevice *dev = dev_get_parent(pdev);
+ struct dsa_priv *priv = dev_get_uclass_priv(dev);
+ struct udevice *master = dsa_get_master(dev);
+ struct dsa_ops *ops = dsa_get_ops(dev);
+ int err;
+
+ if (ops->port_enable) {
+ struct dsa_port_pdata *port_pdata;
+
+ port_pdata = dev_get_parent_plat(pdev);
+ err = ops->port_enable(dev, port_pdata->index,
+ port_pdata->phy);
+ if (err)
+ return err;
+
+ err = ops->port_enable(dev, priv->cpu_port,
+ priv->cpu_port_fixed_phy);
+ if (err)
+ return err;
+ }
+
+ return eth_get_ops(master)->start(master);
+}
+
+/* Stop the desired port, the CPU port and the master Eth interface */
+static void dsa_port_stop(struct udevice *pdev)
+{
+ struct udevice *dev = dev_get_parent(pdev);
+ struct dsa_priv *priv = dev_get_uclass_priv(dev);
+ struct udevice *master = dsa_get_master(dev);
+ struct dsa_ops *ops = dsa_get_ops(dev);
+
+ if (ops->port_disable) {
+ struct dsa_port_pdata *port_pdata;
+
+ port_pdata = dev_get_parent_plat(pdev);
+ ops->port_disable(dev, port_pdata->index, port_pdata->phy);
+ ops->port_disable(dev, priv->cpu_port, priv->cpu_port_fixed_phy);
+ }
+
+ eth_get_ops(master)->stop(master);
+}
+
+/*
+ * Insert a DSA tag and call master Ethernet send on the resulting packet
+ * We copy the frame to a stack buffer where we have reserved headroom and
+ * tailroom space. Headroom and tailroom are set to 0.
+ */
+static int dsa_port_send(struct udevice *pdev, void *packet, int length)
+{
+ struct udevice *dev = dev_get_parent(pdev);
+ struct dsa_priv *priv = dev_get_uclass_priv(dev);
+ int head = priv->headroom, tail = priv->tailroom;
+ struct udevice *master = dsa_get_master(dev);
+ struct dsa_ops *ops = dsa_get_ops(dev);
+ uchar dsa_packet_tmp[PKTSIZE_ALIGN];
+ struct dsa_port_pdata *port_pdata;
+ int err;
+
+ if (ops->xmit) {
+ if (length + head + tail > PKTSIZE_ALIGN)
+ return -EINVAL;
+
+ memset(dsa_packet_tmp, 0, head);
+ memset(dsa_packet_tmp + head + length, 0, tail);
+ memcpy(dsa_packet_tmp + head, packet, length);
+ length += head + tail;
+ /* copy back to preserve original buffer alignment */
+ memcpy(packet, dsa_packet_tmp, length);
+
+ port_pdata = dev_get_parent_plat(pdev);
+ err = ops->xmit(dev, port_pdata->index, packet, length);
+ if (err)
+ return err;
+ }
+
+ return eth_get_ops(master)->send(master, packet, length);
+}
+
+/* Receive a frame from master Ethernet, process it and pass it on */
+static int dsa_port_recv(struct udevice *pdev, int flags, uchar **packetp)
+{
+ struct udevice *dev = dev_get_parent(pdev);
+ struct dsa_priv *priv = dev_get_uclass_priv(dev);
+ int head = priv->headroom, tail = priv->tailroom;
+ struct udevice *master = dsa_get_master(dev);
+ struct dsa_ops *ops = dsa_get_ops(dev);
+ struct dsa_port_pdata *port_pdata;
+ int length, port_index, err;
+
+ length = eth_get_ops(master)->recv(master, flags, packetp);
+ if (length <= 0 || !ops->rcv)
+ return length;
+
+ /*
+ * If we receive frames from a different port or frames that DSA driver
+ * doesn't like we discard them here.
+ * In case of discard we return with no frame and expect to be called
+ * again instead of looping here, so upper layer can deal with timeouts.
+ */
+ port_pdata = dev_get_parent_plat(pdev);
+ err = ops->rcv(dev, &port_index, *packetp, length);
+ if (err || port_index != port_pdata->index || (length <= head + tail)) {
+ if (eth_get_ops(master)->free_pkt)
+ eth_get_ops(master)->free_pkt(master, *packetp, length);
+ return -EAGAIN;
+ }
+
+ /*
+ * We move the pointer over headroom here to avoid a copy. If free_pkt
+ * gets called we move the pointer back before calling master free_pkt.
+ */
+ *packetp += head;
+
+ return length - head - tail;
+}
+
+static int dsa_port_free_pkt(struct udevice *pdev, uchar *packet, int length)
+{
+ struct udevice *dev = dev_get_parent(pdev);
+ struct udevice *master = dsa_get_master(dev);
+ struct dsa_priv *priv;
+
+ priv = dev_get_uclass_priv(dev);
+ if (eth_get_ops(master)->free_pkt) {
+ /* return the original pointer and length to master Eth */
+ packet -= priv->headroom;
+ length += priv->headroom - priv->tailroom;
+
+ return eth_get_ops(master)->free_pkt(master, packet, length);
+ }
+
+ return 0;
+}
+
+static int dsa_port_of_to_pdata(struct udevice *pdev)
+{
+ struct dsa_port_pdata *port_pdata;
+ struct eth_pdata *eth_pdata;
+ const char *label;
+ u32 index;
+ int err;
+
+ if (!pdev)
+ return -ENODEV;
+
+ err = ofnode_read_u32(dev_ofnode(pdev), "reg", &index);
+ if (err)
+ return err;
+
+ port_pdata = dev_get_parent_plat(pdev);
+ port_pdata->index = index;
+
+ label = ofnode_read_string(dev_ofnode(pdev), "label");
+ if (label)
+ strlcpy(port_pdata->name, label, DSA_PORT_NAME_LENGTH);
+
+ eth_pdata = dev_get_plat(pdev);
+ eth_pdata->priv_pdata = port_pdata;
+
+ dev_dbg(pdev, "port %d node %s\n", port_pdata->index,
+ ofnode_get_name(dev_ofnode(pdev)));
+
+ return 0;
+}
+
+static const struct eth_ops dsa_port_ops = {
+ .start = dsa_port_start,
+ .send = dsa_port_send,
+ .recv = dsa_port_recv,
+ .stop = dsa_port_stop,
+ .free_pkt = dsa_port_free_pkt,
+};
+
+/*
+ * Inherit port's hwaddr from the DSA master, unless the port already has a
+ * unique MAC address specified in the environment.
+ */
+static void dsa_port_set_hwaddr(struct udevice *pdev, struct udevice *master)
+{
+ struct eth_pdata *eth_pdata, *master_pdata;
+ unsigned char env_enetaddr[ARP_HLEN];
+
+ eth_env_get_enetaddr_by_index("eth", dev_seq(pdev), env_enetaddr);
+ if (!is_zero_ethaddr(env_enetaddr)) {
+ /* individual port mac addrs require master to be promisc */
+ struct eth_ops *eth_ops = eth_get_ops(master);
+
+ if (eth_ops->set_promisc)
+ eth_ops->set_promisc(master, true);
+
+ return;
+ }
+
+ master_pdata = dev_get_plat(master);
+ eth_pdata = dev_get_plat(pdev);
+ memcpy(eth_pdata->enetaddr, master_pdata->enetaddr, ARP_HLEN);
+ eth_env_set_enetaddr_by_index("eth", dev_seq(pdev),
+ master_pdata->enetaddr);
+}
+
+static int dsa_port_probe(struct udevice *pdev)
+{
+ struct udevice *dev = dev_get_parent(pdev);
+ struct dsa_ops *ops = dsa_get_ops(dev);
+ struct dsa_port_pdata *port_pdata;
+ struct udevice *master;
+ int err;
+
+ port_pdata = dev_get_parent_plat(pdev);
+
+ port_pdata->phy = dm_eth_phy_connect(pdev);
+ if (!port_pdata->phy)
+ return -ENODEV;
+
+ master = dsa_get_master(dev);
+ if (!master)
+ return -ENODEV;
+
+ /*
+ * Probe the master device. We depend on the master device for proper
+ * operation and we also need it for MAC inheritance below.
+ *
+ * TODO: we assume the master device is always there and doesn't get
+ * removed during runtime.
+ */
+ err = device_probe(master);
+ if (err)
+ return err;
+
+ dsa_port_set_hwaddr(pdev, master);
+
+ if (ops->port_probe) {
+ err = ops->port_probe(dev, port_pdata->index,
+ port_pdata->phy);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int dsa_port_remove(struct udevice *pdev)
+{
+ struct dsa_port_pdata *port_pdata = dev_get_parent_plat(pdev);
+
+ port_pdata->phy = NULL;
+
+ return 0;
+}
+
+U_BOOT_DRIVER(dsa_port) = {
+ .name = DSA_PORT_CHILD_DRV_NAME,
+ .id = UCLASS_ETH,
+ .ops = &dsa_port_ops,
+ .probe = dsa_port_probe,
+ .remove = dsa_port_remove,
+ .of_to_plat = dsa_port_of_to_pdata,
+ .plat_auto = sizeof(struct eth_pdata),
+};
+
+static int dsa_sanitize_ops(struct udevice *dev)
+{
+ struct dsa_ops *ops = dsa_get_ops(dev);
+
+ if ((!ops->xmit || !ops->rcv) &&
+ (!ops->port_enable && !ops->port_disable)) {
+ dev_err(dev, "Packets cannot be steered to ports\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * This function mostly deals with pulling information out of the device tree
+ * into the pdata structure.
+ * It goes through the list of switch ports, registers an eth device for each
+ * front panel port and identifies the cpu port connected to master eth device.
+ * TODO: support cascaded switches
+ */
+static int dsa_post_bind(struct udevice *dev)
+{
+ struct dsa_pdata *pdata = dev_get_uclass_plat(dev);
+ ofnode node = dev_ofnode(dev), pnode;
+ int i, err, first_err = 0;
+
+ if (!ofnode_valid(node))
+ return -ENODEV;
+
+ err = dsa_sanitize_ops(dev);
+ if (err)
+ return err;
+
+ pdata->master_node = ofnode_null();
+
+ node = ofnode_find_subnode(node, "ports");
+ if (!ofnode_valid(node))
+ node = ofnode_find_subnode(dev_ofnode(dev), "ethernet-ports");
+ if (!ofnode_valid(node)) {
+ dev_err(dev, "ports node is missing under DSA device!\n");
+ return -EINVAL;
+ }
+
+ pdata->num_ports = ofnode_get_child_count(node);
+ if (pdata->num_ports <= 0 || pdata->num_ports > DSA_MAX_PORTS) {
+ dev_err(dev, "invalid number of ports (%d)\n",
+ pdata->num_ports);
+ return -EINVAL;
+ }
+
+ /* look for the CPU port */
+ ofnode_for_each_subnode(pnode, node) {
+ u32 ethernet;
+
+ if (ofnode_read_u32(pnode, "ethernet", &ethernet))
+ continue;
+
+ pdata->master_node = ofnode_get_by_phandle(ethernet);
+ pdata->cpu_port_node = pnode;
+ break;
+ }
+
+ if (!ofnode_valid(pdata->master_node)) {
+ dev_err(dev, "master eth node missing!\n");
+ return -EINVAL;
+ }
+
+ if (ofnode_read_u32(pnode, "reg", &pdata->cpu_port)) {
+ dev_err(dev, "CPU port node not valid!\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "master node %s on port %d\n",
+ ofnode_get_name(pdata->master_node), pdata->cpu_port);
+
+ for (i = 0; i < pdata->num_ports; i++) {
+ char name[DSA_PORT_NAME_LENGTH];
+ struct udevice *pdev;
+
+ /*
+ * If this is the CPU port don't register it as an ETH device,
+ * we skip it on purpose since I/O to/from it from the CPU
+ * isn't useful.
+ */
+ if (i == pdata->cpu_port)
+ continue;
+
+ /*
+ * Set up default port names. If present, DT port labels
+ * will override the default port names.
+ */
+ snprintf(name, DSA_PORT_NAME_LENGTH, "%s@%d", dev->name, i);
+
+ ofnode_for_each_subnode(pnode, node) {
+ u32 reg;
+
+ if (ofnode_read_u32(pnode, "reg", &reg))
+ continue;
+
+ if (reg == i)
+ break;
+ }
+
+ /*
+ * skip registration if port id not found or if the port
+ * is explicitly disabled in DT
+ */
+ if (!ofnode_valid(pnode) || !ofnode_is_enabled(pnode))
+ continue;
+
+ err = device_bind_driver_to_node(dev, DSA_PORT_CHILD_DRV_NAME,
+ name, pnode, &pdev);
+ if (pdev) {
+ struct dsa_port_pdata *port_pdata;
+
+ port_pdata = dev_get_parent_plat(pdev);
+ strlcpy(port_pdata->name, name, DSA_PORT_NAME_LENGTH);
+ pdev->name = port_pdata->name;
+ }
+
+ /* try to bind all ports but keep 1st error */
+ if (err && !first_err)
+ first_err = err;
+ }
+
+ if (first_err)
+ return first_err;
+
+ dev_dbg(dev, "DSA ports successfully bound\n");
+
+ return 0;
+}
+
+/**
+ * Initialize the uclass per device internal state structure (priv).
+ * TODO: pick up references to other switch devices here, if we're cascaded.
+ */
+static int dsa_pre_probe(struct udevice *dev)
+{
+ struct dsa_pdata *pdata = dev_get_uclass_plat(dev);
+ struct dsa_priv *priv = dev_get_uclass_priv(dev);
+ int err;
+
+ priv->num_ports = pdata->num_ports;
+ priv->cpu_port = pdata->cpu_port;
+ priv->cpu_port_fixed_phy = fixed_phy_create(pdata->cpu_port_node);
+ if (!priv->cpu_port_fixed_phy) {
+ dev_err(dev, "Failed to register fixed-link for CPU port\n");
+ return -ENODEV;
+ }
+
+ err = uclass_get_device_by_ofnode(UCLASS_ETH, pdata->master_node,
+ &priv->master_dev);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int dsa_post_probe(struct udevice *dev)
+{
+ struct dsa_priv *priv = dev_get_uclass_priv(dev);
+ struct dsa_ops *ops = dsa_get_ops(dev);
+ int err;
+
+ /* Simulate a probing event for the CPU port */
+ if (ops->port_probe) {
+ err = ops->port_probe(dev, priv->cpu_port,
+ priv->cpu_port_fixed_phy);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+UCLASS_DRIVER(dsa) = {
+ .id = UCLASS_DSA,
+ .name = "dsa",
+ .post_bind = dsa_post_bind,
+ .pre_probe = dsa_pre_probe,
+ .post_probe = dsa_post_probe,
+ .per_device_auto = sizeof(struct dsa_priv),
+ .per_device_plat_auto = sizeof(struct dsa_pdata),
+ .per_child_plat_auto = sizeof(struct dsa_port_pdata),
+ .flags = DM_UC_FLAG_SEQ_ALIAS,
+};
diff --git a/net/eth-uclass.c b/net/eth-uclass.c
new file mode 100644
index 00000000000..e34d7af0229
--- /dev/null
+++ b/net/eth-uclass.c
@@ -0,0 +1,657 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2001-2015
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ * Joe Hershberger, National Instruments
+ */
+
+#define LOG_CATEGORY UCLASS_ETH
+
+#include <bootdev.h>
+#include <bootstage.h>
+#include <dm.h>
+#include <env.h>
+#include <log.h>
+#include <net.h>
+#include <nvmem.h>
+#include <asm/global_data.h>
+#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
+#include <net/pcap.h>
+#include "eth_internal.h"
+#include <eth_phy.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/**
+ * struct eth_device_priv - private structure for each Ethernet device
+ *
+ * @state: The state of the Ethernet MAC driver (defined by enum eth_state_t)
+ */
+struct eth_device_priv {
+ enum eth_state_t state;
+ bool running;
+};
+
+/**
+ * struct eth_uclass_priv - The structure attached to the uclass itself
+ *
+ * @current: The Ethernet device that the network functions are using
+ * @no_bootdevs: true to skip binding Ethernet bootdevs (this is a negative flag
+ * so that the default value enables it)
+ */
+struct eth_uclass_priv {
+ struct udevice *current;
+ bool no_bootdevs;
+};
+
+/* eth_errno - This stores the most recent failure code from DM functions */
+static int eth_errno;
+/* Are we currently in eth_init() or eth_halt()? */
+static bool in_init_halt;
+
+/* board-specific Ethernet Interface initializations. */
+__weak int board_interface_eth_init(struct udevice *dev,
+ phy_interface_t interface_type)
+{
+ return 0;
+}
+
+static struct eth_uclass_priv *eth_get_uclass_priv(void)
+{
+ struct uclass *uc;
+ int ret;
+
+ ret = uclass_get(UCLASS_ETH, &uc);
+ if (ret)
+ return NULL;
+
+ assert(uc);
+ return uclass_get_priv(uc);
+}
+
+void eth_set_enable_bootdevs(bool enable)
+{
+ struct eth_uclass_priv *priv = eth_get_uclass_priv();
+
+ if (priv)
+ priv->no_bootdevs = !enable;
+}
+
+void eth_set_current_to_next(void)
+{
+ struct eth_uclass_priv *uc_priv;
+
+ uc_priv = eth_get_uclass_priv();
+ if (uc_priv->current)
+ uclass_next_device(&uc_priv->current);
+ if (!uc_priv->current)
+ uclass_first_device(UCLASS_ETH, &uc_priv->current);
+}
+
+/*
+ * Typically this will simply return the active device.
+ * In the case where the most recent active device was unset, this will attempt
+ * to return the device with sequence id 0 (which can be configured by the
+ * device tree). If this fails, fall back to just getting the first device.
+ * The latter is non-deterministic and depends on the order of the probing.
+ * If that device doesn't exist or fails to probe, this function will return
+ * NULL.
+ */
+struct udevice *eth_get_dev(void)
+{
+ struct eth_uclass_priv *uc_priv;
+
+ uc_priv = eth_get_uclass_priv();
+ if (!uc_priv)
+ return NULL;
+
+ if (!uc_priv->current) {
+ eth_errno = uclass_get_device_by_seq(UCLASS_ETH, 0,
+ &uc_priv->current);
+ if (eth_errno)
+ eth_errno = uclass_first_device_err(UCLASS_ETH,
+ &uc_priv->current);
+ if (eth_errno)
+ uc_priv->current = NULL;
+ }
+ return uc_priv->current;
+}
+
+/*
+ * Typically this will just store a device pointer.
+ * In case it was not probed, we will attempt to do so.
+ * dev may be NULL to unset the active device.
+ */
+void eth_set_dev(struct udevice *dev)
+{
+ if (dev && !device_active(dev)) {
+ eth_errno = device_probe(dev);
+ if (eth_errno)
+ dev = NULL;
+ }
+
+ eth_get_uclass_priv()->current = dev;
+}
+
+/*
+ * Find the udevice that either has the name passed in as devname or has an
+ * alias named devname.
+ */
+struct udevice *eth_get_dev_by_name(const char *devname)
+{
+ int seq = -1;
+ char *endp = NULL;
+ const char *startp = NULL;
+ struct udevice *it;
+ struct uclass *uc;
+ int len = strlen("eth");
+ int ret;
+
+ /* Must be longer than 3 to be an alias */
+ if (!strncmp(devname, "eth", len) && strlen(devname) > len) {
+ startp = devname + len;
+ seq = dectoul(startp, &endp);
+ }
+
+ ret = uclass_get(UCLASS_ETH, &uc);
+ if (ret)
+ return NULL;
+
+ uclass_foreach_dev(it, uc) {
+ /*
+ * We don't care about errors from probe here. Either they won't
+ * match an alias or it will match a literal name and we'll pick
+ * up the error when we try to probe again in eth_set_dev().
+ */
+ if (device_probe(it))
+ continue;
+ /* Check for the name or the sequence number to match */
+ if (strcmp(it->name, devname) == 0 ||
+ (endp > startp && dev_seq(it) == seq))
+ return it;
+ }
+
+ return NULL;
+}
+
+unsigned char *eth_get_ethaddr(void)
+{
+ struct eth_pdata *pdata;
+
+ if (eth_get_dev()) {
+ pdata = dev_get_plat(eth_get_dev());
+ return pdata->enetaddr;
+ }
+
+ return NULL;
+}
+
+/* Set active state without calling start on the driver */
+int eth_init_state_only(void)
+{
+ struct udevice *current;
+ struct eth_device_priv *priv;
+
+ current = eth_get_dev();
+ if (!current || !device_active(current))
+ return -EINVAL;
+
+ priv = dev_get_uclass_priv(current);
+ priv->state = ETH_STATE_ACTIVE;
+
+ return 0;
+}
+
+/* Set passive state without calling stop on the driver */
+void eth_halt_state_only(void)
+{
+ struct udevice *current;
+ struct eth_device_priv *priv;
+
+ current = eth_get_dev();
+ if (!current || !device_active(current))
+ return;
+
+ priv = dev_get_uclass_priv(current);
+ priv->state = ETH_STATE_PASSIVE;
+}
+
+int eth_get_dev_index(void)
+{
+ if (eth_get_dev())
+ return dev_seq(eth_get_dev());
+ return -1;
+}
+
+static int eth_write_hwaddr(struct udevice *dev)
+{
+ struct eth_pdata *pdata;
+ int ret = 0;
+
+ if (!dev || !device_active(dev))
+ return -EINVAL;
+
+ /* seq is valid since the device is active */
+ if (eth_get_ops(dev)->write_hwaddr && !eth_mac_skip(dev_seq(dev))) {
+ pdata = dev_get_plat(dev);
+ if (!is_valid_ethaddr(pdata->enetaddr)) {
+ printf("\nError: %s address %pM illegal value\n",
+ dev->name, pdata->enetaddr);
+ return -EINVAL;
+ }
+
+ /*
+ * Drivers are allowed to decide not to implement this at
+ * run-time. E.g. Some devices may use it and some may not.
+ */
+ ret = eth_get_ops(dev)->write_hwaddr(dev);
+ if (ret == -ENOSYS)
+ ret = 0;
+ if (ret)
+ printf("\nWarning: %s failed to set MAC address\n",
+ dev->name);
+ }
+
+ return ret;
+}
+
+static int on_ethaddr(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ int index;
+ int retval;
+ struct udevice *dev;
+
+ /* look for an index after "eth" */
+ index = dectoul(name + 3, NULL);
+
+ retval = uclass_find_device_by_seq(UCLASS_ETH, index, &dev);
+ if (!retval) {
+ struct eth_pdata *pdata = dev_get_plat(dev);
+ switch (op) {
+ case env_op_create:
+ case env_op_overwrite:
+ string_to_enetaddr(value, pdata->enetaddr);
+ eth_write_hwaddr(dev);
+ break;
+ case env_op_delete:
+ memset(pdata->enetaddr, 0, ARP_HLEN);
+ }
+ }
+
+ return 0;
+}
+U_BOOT_ENV_CALLBACK(ethaddr, on_ethaddr);
+
+int eth_init(void)
+{
+ struct udevice *current = NULL;
+ struct udevice *old_current;
+ int ret = -ENODEV;
+ char *ethrotate;
+ char *ethact;
+
+ if (in_init_halt)
+ return -EBUSY;
+
+ in_init_halt = true;
+
+ ethact = env_get("ethact");
+ ethrotate = env_get("ethrotate");
+
+ /*
+ * When 'ethrotate' variable is set to 'no' and 'ethact' variable
+ * is already set to an ethernet device, we should stick to 'ethact'.
+ */
+ if ((ethrotate != NULL) && (strcmp(ethrotate, "no") == 0)) {
+ if (ethact) {
+ current = eth_get_dev_by_name(ethact);
+ if (!current) {
+ ret = -EINVAL;
+ goto end;
+ }
+ }
+ }
+
+ if (!current) {
+ current = eth_get_dev();
+ if (!current) {
+ log_err("No ethernet found.\n");
+ ret = -ENODEV;
+ goto end;
+ }
+ }
+
+ old_current = current;
+ do {
+ if (current) {
+ debug("Trying %s\n", current->name);
+
+ if (device_active(current)) {
+ ret = eth_get_ops(current)->start(current);
+ if (ret >= 0) {
+ struct eth_device_priv *priv =
+ dev_get_uclass_priv(current);
+
+ priv->state = ETH_STATE_ACTIVE;
+ priv->running = true;
+ ret = 0;
+ goto end;
+ }
+ } else {
+ ret = eth_errno;
+ }
+
+ debug("FAIL\n");
+ } else {
+ debug("PROBE FAIL\n");
+ }
+
+ /*
+ * If ethrotate is enabled, this will change "current",
+ * otherwise we will drop out of this while loop immediately
+ */
+ eth_try_another(0);
+ /* This will ensure the new "current" attempted to probe */
+ current = eth_get_dev();
+ } while (old_current != current);
+
+end:
+ in_init_halt = false;
+ return ret;
+}
+
+void eth_halt(void)
+{
+ struct udevice *current;
+ struct eth_device_priv *priv;
+
+ if (in_init_halt)
+ return;
+
+ in_init_halt = true;
+
+ current = eth_get_dev();
+ if (!current)
+ goto end;
+
+ priv = dev_get_uclass_priv(current);
+ if (!priv || !priv->running)
+ goto end;
+
+ eth_get_ops(current)->stop(current);
+ priv->state = ETH_STATE_PASSIVE;
+ priv->running = false;
+
+end:
+ in_init_halt = false;
+}
+
+int eth_is_active(struct udevice *dev)
+{
+ struct eth_device_priv *priv;
+
+ if (!dev || !device_active(dev))
+ return 0;
+
+ priv = dev_get_uclass_priv(dev);
+ return priv->state == ETH_STATE_ACTIVE;
+}
+
+int eth_send(void *packet, int length)
+{
+ struct udevice *current;
+ int ret;
+
+ current = eth_get_dev();
+ if (!current)
+ return -ENODEV;
+
+ if (!eth_is_active(current))
+ return -EINVAL;
+
+ ret = eth_get_ops(current)->send(current, packet, length);
+ if (ret < 0) {
+ /* We cannot completely return the error at present */
+ debug("%s: send() returned error %d\n", __func__, ret);
+ }
+#if defined(CONFIG_CMD_PCAP)
+ if (ret >= 0)
+ pcap_post(packet, length, true);
+#endif
+ return ret;
+}
+
+int eth_rx(void)
+{
+ struct udevice *current;
+ uchar *packet;
+ int flags;
+ int ret;
+ int i;
+
+ current = eth_get_dev();
+ if (!current)
+ return -ENODEV;
+
+ if (!eth_is_active(current))
+ return -EINVAL;
+
+ /* Process up to 32 packets at one time */
+ flags = ETH_RECV_CHECK_DEVICE;
+ for (i = 0; i < ETH_PACKETS_BATCH_RECV; i++) {
+ ret = eth_get_ops(current)->recv(current, flags, &packet);
+ flags = 0;
+ if (ret > 0)
+ net_process_received_packet(packet, ret);
+ if (ret >= 0 && eth_get_ops(current)->free_pkt)
+ eth_get_ops(current)->free_pkt(current, packet, ret);
+ if (ret <= 0)
+ break;
+ }
+ if (ret == -EAGAIN)
+ ret = 0;
+ if (ret < 0) {
+ /* We cannot completely return the error at present */
+ debug("%s: recv() returned error %d\n", __func__, ret);
+ }
+ return ret;
+}
+
+int eth_initialize(void)
+{
+ int num_devices = 0;
+ struct udevice *dev;
+
+ eth_common_init();
+
+ /*
+ * Devices need to write the hwaddr even if not started so that Linux
+ * will have access to the hwaddr that u-boot stored for the device.
+ * This is accomplished by attempting to probe each device and calling
+ * their write_hwaddr() operation.
+ */
+ uclass_first_device_check(UCLASS_ETH, &dev);
+ if (!dev) {
+ log_err("No ethernet found.\n");
+ bootstage_error(BOOTSTAGE_ID_NET_ETH_START);
+ } else {
+ char *ethprime = env_get("ethprime");
+ struct udevice *prime_dev = NULL;
+
+ if (ethprime)
+ prime_dev = eth_get_dev_by_name(ethprime);
+ if (prime_dev) {
+ eth_set_dev(prime_dev);
+ eth_current_changed();
+ } else {
+ eth_set_dev(NULL);
+ }
+
+ bootstage_mark(BOOTSTAGE_ID_NET_ETH_INIT);
+ do {
+ if (device_active(dev)) {
+ if (num_devices)
+ printf(", ");
+
+ printf("eth%d: %s", dev_seq(dev), dev->name);
+
+ if (ethprime && dev == prime_dev)
+ printf(" [PRIME]");
+ }
+
+ eth_write_hwaddr(dev);
+
+ if (device_active(dev))
+ num_devices++;
+ uclass_next_device_check(&dev);
+ } while (dev);
+
+ if (!num_devices)
+ log_err("No ethernet found.\n");
+ putc('\n');
+ }
+
+ return num_devices;
+}
+
+static int eth_post_bind(struct udevice *dev)
+{
+ struct eth_uclass_priv *priv = uclass_get_priv(dev->uclass);
+ int ret;
+
+ if (strchr(dev->name, ' ')) {
+ printf("\nError: eth device name \"%s\" has a space!\n",
+ dev->name);
+ return -EINVAL;
+ }
+
+#ifdef CONFIG_DM_ETH_PHY
+ eth_phy_binds_nodes(dev);
+#endif
+ if (CONFIG_IS_ENABLED(BOOTDEV_ETH) && !priv->no_bootdevs) {
+ ret = bootdev_setup_for_dev(dev, "eth_bootdev");
+ if (ret)
+ return log_msg_ret("bootdev", ret);
+ }
+
+ return 0;
+}
+
+static int eth_pre_unbind(struct udevice *dev)
+{
+ /* Don't hang onto a pointer that is going away */
+ if (dev == eth_get_uclass_priv()->current)
+ eth_set_dev(NULL);
+
+ return 0;
+}
+
+static bool eth_dev_get_mac_address(struct udevice *dev, u8 mac[ARP_HLEN])
+{
+#if CONFIG_IS_ENABLED(OF_CONTROL)
+ const uint8_t *p;
+ struct nvmem_cell mac_cell;
+
+ p = dev_read_u8_array_ptr(dev, "mac-address", ARP_HLEN);
+ if (!p)
+ p = dev_read_u8_array_ptr(dev, "local-mac-address", ARP_HLEN);
+
+ if (p) {
+ memcpy(mac, p, ARP_HLEN);
+ return true;
+ }
+
+ if (nvmem_cell_get_by_name(dev, "mac-address", &mac_cell))
+ return false;
+
+ return !nvmem_cell_read(&mac_cell, mac, ARP_HLEN);
+#else
+ return false;
+#endif
+}
+
+static int eth_post_probe(struct udevice *dev)
+{
+ struct eth_device_priv *priv = dev_get_uclass_priv(dev);
+ struct eth_pdata *pdata = dev_get_plat(dev);
+ unsigned char env_enetaddr[ARP_HLEN];
+ char *source = "DT";
+
+ priv->state = ETH_STATE_INIT;
+ priv->running = false;
+
+ /* Check if the device has a valid MAC address in device tree */
+ if (!eth_dev_get_mac_address(dev, pdata->enetaddr) ||
+ !is_valid_ethaddr(pdata->enetaddr)) {
+ /* Check if the device has a MAC address in ROM */
+ if (eth_get_ops(dev)->read_rom_hwaddr) {
+ int ret;
+
+ ret = eth_get_ops(dev)->read_rom_hwaddr(dev);
+ if (!ret)
+ source = "ROM";
+ }
+ }
+
+ eth_env_get_enetaddr_by_index("eth", dev_seq(dev), env_enetaddr);
+ if (!is_zero_ethaddr(env_enetaddr)) {
+ if (!is_zero_ethaddr(pdata->enetaddr) &&
+ memcmp(pdata->enetaddr, env_enetaddr, ARP_HLEN)) {
+ printf("\nWarning: %s MAC addresses don't match:\n",
+ dev->name);
+ printf("Address in %s is\t\t%pM\n",
+ source, pdata->enetaddr);
+ printf("Address in environment is\t%pM\n",
+ env_enetaddr);
+ }
+
+ /* Override the ROM MAC address */
+ memcpy(pdata->enetaddr, env_enetaddr, ARP_HLEN);
+ } else if (is_valid_ethaddr(pdata->enetaddr)) {
+ eth_env_set_enetaddr_by_index("eth", dev_seq(dev),
+ pdata->enetaddr);
+ } else if (is_zero_ethaddr(pdata->enetaddr) ||
+ !is_valid_ethaddr(pdata->enetaddr)) {
+#ifdef CONFIG_NET_RANDOM_ETHADDR
+ net_random_ethaddr(pdata->enetaddr);
+ printf("\nWarning: %s (eth%d) using random MAC address - %pM\n",
+ dev->name, dev_seq(dev), pdata->enetaddr);
+ eth_env_set_enetaddr_by_index("eth", dev_seq(dev),
+ pdata->enetaddr);
+#else
+ printf("\nError: %s No valid MAC address found.\n",
+ dev->name);
+ return -EINVAL;
+#endif
+ }
+
+ eth_write_hwaddr(dev);
+
+ return 0;
+}
+
+static int eth_pre_remove(struct udevice *dev)
+{
+ struct eth_pdata *pdata = dev_get_plat(dev);
+
+ eth_get_ops(dev)->stop(dev);
+
+ /* clear the MAC address */
+ memset(pdata->enetaddr, 0, ARP_HLEN);
+
+ return 0;
+}
+
+UCLASS_DRIVER(ethernet) = {
+ .name = "ethernet",
+ .id = UCLASS_ETH,
+ .post_bind = eth_post_bind,
+ .pre_unbind = eth_pre_unbind,
+ .post_probe = eth_post_probe,
+ .pre_remove = eth_pre_remove,
+ .priv_auto = sizeof(struct eth_uclass_priv),
+ .per_device_auto = sizeof(struct eth_device_priv),
+ .flags = DM_UC_FLAG_SEQ_ALIAS,
+};
diff --git a/net/eth_bootdev.c b/net/eth_bootdev.c
new file mode 100644
index 00000000000..6ee54e3c790
--- /dev/null
+++ b/net/eth_bootdev.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootdev for ethernet (uses PXE)
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <bootdev.h>
+#include <bootflow.h>
+#include <command.h>
+#include <bootmeth.h>
+#include <dm.h>
+#include <extlinux.h>
+#include <init.h>
+#include <log.h>
+#include <net.h>
+#include <test/test.h>
+
+static int eth_get_bootflow(struct udevice *dev, struct bootflow_iter *iter,
+ struct bootflow *bflow)
+{
+ char name[60];
+ int ret;
+
+ /* Must be an Ethernet device */
+ ret = bootflow_iter_check_net(iter);
+ if (ret)
+ return log_msg_ret("net", ret);
+
+ ret = bootmeth_check(bflow->method, iter);
+ if (ret)
+ return log_msg_ret("check", ret);
+
+ /*
+ * Like extlinux boot, this assumes there is only one Ethernet device.
+ * In this case, that means that @eth is ignored
+ */
+
+ snprintf(name, sizeof(name), "%s.%d", dev->name, iter->part);
+ bflow->name = strdup(name);
+ if (!bflow->name)
+ return log_msg_ret("name", -ENOMEM);
+
+ /* See distro_pxe_read_bootflow() for the standard impl of this */
+ log_debug("dhcp complete - reading bootflow with method '%s'\n",
+ bflow->method->name);
+ ret = bootmeth_read_bootflow(bflow->method, bflow);
+ log_debug("reading bootflow returned %d\n", ret);
+ if (ret)
+ return log_msg_ret("method", ret);
+
+ return 0;
+}
+
+static int eth_bootdev_bind(struct udevice *dev)
+{
+ struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev);
+
+ ucp->prio = BOOTDEVP_6_NET_BASE;
+
+ return 0;
+}
+
+static int eth_bootdev_hunt(struct bootdev_hunter *info, bool show)
+{
+ int ret;
+
+ if (!test_eth_enabled())
+ return 0;
+
+ /* init PCI first since this is often used to provide Ehternet */
+ if (IS_ENABLED(CONFIG_PCI)) {
+ ret = pci_init();
+ if (ret)
+ log_warning("Failed to init PCI (%dE)\n", ret);
+ }
+
+ /*
+ * Ethernet devices can also come from USB, but that is a higher
+ * priority (BOOTDEVP_5_SCAN_SLOW) than ethernet, so it should have been
+ * enumerated already. If something like 'bootflow scan dhcp' is used
+ * then the user will need to run 'usb start' first.
+ */
+ if (IS_ENABLED(CONFIG_CMD_DHCP)) {
+ ret = dhcp_run(0, NULL, false);
+ if (ret)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+struct bootdev_ops eth_bootdev_ops = {
+ .get_bootflow = eth_get_bootflow,
+};
+
+static const struct udevice_id eth_bootdev_ids[] = {
+ { .compatible = "u-boot,bootdev-eth" },
+ { }
+};
+
+U_BOOT_DRIVER(eth_bootdev) = {
+ .name = "eth_bootdev",
+ .id = UCLASS_BOOTDEV,
+ .ops = &eth_bootdev_ops,
+ .bind = eth_bootdev_bind,
+ .of_match = eth_bootdev_ids,
+};
+
+BOOTDEV_HUNTER(eth_bootdev_hunt) = {
+ .prio = BOOTDEVP_6_NET_BASE,
+ .uclass = UCLASS_ETH,
+ .hunt = eth_bootdev_hunt,
+ .drv = DM_DRIVER_REF(eth_bootdev),
+};
diff --git a/net/eth_common.c b/net/eth_common.c
new file mode 100644
index 00000000000..89b5bb37189
--- /dev/null
+++ b/net/eth_common.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2001-2015
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ * Joe Hershberger, National Instruments
+ */
+
+#include <bootstage.h>
+#include <dm.h>
+#include <env.h>
+#include <miiphy.h>
+#include <net.h>
+#include "eth_internal.h"
+
+int eth_env_get_enetaddr_by_index(const char *base_name, int index,
+ uchar *enetaddr)
+{
+ char enetvar[32];
+ sprintf(enetvar, index ? "%s%daddr" : "%saddr", base_name, index);
+ return eth_env_get_enetaddr(enetvar, enetaddr);
+}
+
+int eth_env_set_enetaddr_by_index(const char *base_name, int index,
+ uchar *enetaddr)
+{
+ char enetvar[32];
+ sprintf(enetvar, index ? "%s%daddr" : "%saddr", base_name, index);
+ return eth_env_set_enetaddr(enetvar, enetaddr);
+}
+
+void eth_common_init(void)
+{
+ bootstage_mark(BOOTSTAGE_ID_NET_ETH_START);
+#if CONFIG_IS_ENABLED(ETH)
+#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB)
+ miiphy_init();
+#endif
+#endif
+}
+
+int eth_mac_skip(int index)
+{
+ char enetvar[15];
+ char *skip_state;
+
+ sprintf(enetvar, index ? "eth%dmacskip" : "ethmacskip", index);
+ skip_state = env_get(enetvar);
+ return skip_state != NULL;
+}
+
+void eth_current_changed(void)
+{
+ char *act = env_get("ethact");
+ char *ethrotate;
+
+ /*
+ * The call to eth_get_dev() below has a side effect of rotating
+ * ethernet device if uc_priv->current == NULL. This is not what
+ * we want when 'ethrotate' variable is 'no'.
+ */
+ ethrotate = env_get("ethrotate");
+ if ((ethrotate != NULL) && (strcmp(ethrotate, "no") == 0))
+ return;
+
+ /* update current ethernet name */
+ if (eth_get_dev()) {
+ if (act == NULL || strcmp(act, eth_get_name()) != 0)
+ env_set("ethact", eth_get_name());
+ }
+ /*
+ * remove the variable completely if there is no active
+ * interface
+ */
+ else if (act != NULL)
+ env_set("ethact", NULL);
+}
+
+void eth_try_another(int first_restart)
+{
+ static void *first_failed;
+ char *ethrotate;
+
+ /*
+ * Do not rotate between network interfaces when
+ * 'ethrotate' variable is set to 'no'.
+ */
+ ethrotate = env_get("ethrotate");
+ if ((ethrotate != NULL) && (strcmp(ethrotate, "no") == 0))
+ return;
+
+ if (!eth_get_dev())
+ return;
+
+ if (first_restart)
+ first_failed = eth_get_dev();
+
+ eth_set_current_to_next();
+
+ eth_current_changed();
+
+ if (first_failed == eth_get_dev())
+ net_restart_wrap = 1;
+}
+
+void eth_set_current(void)
+{
+ static char *act;
+ static int env_changed_id;
+ int env_id;
+
+ env_id = env_get_id();
+ if ((act == NULL) || (env_changed_id != env_id)) {
+ act = env_get("ethact");
+ env_changed_id = env_id;
+ }
+
+ if (act == NULL) {
+ char *ethprime = env_get("ethprime");
+ void *dev = NULL;
+
+ if (ethprime)
+ dev = eth_get_dev_by_name(ethprime);
+ if (dev)
+ eth_set_dev(dev);
+ else
+ eth_set_dev(NULL);
+ } else {
+ eth_set_dev(eth_get_dev_by_name(act));
+ }
+
+ eth_current_changed();
+}
+
+const char *eth_get_name(void)
+{
+ return eth_get_dev() ? eth_get_dev()->name : "unknown";
+}
diff --git a/net/eth_internal.h b/net/eth_internal.h
new file mode 100644
index 00000000000..cb302c157b5
--- /dev/null
+++ b/net/eth_internal.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Copyright 2001-2015
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ * Joe Hershberger, National Instruments
+ */
+
+#ifndef __ETH_INTERNAL_H
+#define __ETH_INTERNAL_H
+
+/* Do init that is common to driver model and legacy networking */
+void eth_common_init(void);
+
+int eth_mac_skip(int index);
+void eth_current_changed(void);
+void eth_set_dev(struct udevice *dev);
+void eth_set_current_to_next(void);
+
+#endif
diff --git a/net/fastboot_tcp.c b/net/fastboot_tcp.c
new file mode 100644
index 00000000000..d1fccbc7238
--- /dev/null
+++ b/net/fastboot_tcp.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ */
+
+#include <fastboot.h>
+#include <net.h>
+#include <net/fastboot_tcp.h>
+#include <net/tcp.h>
+
+static char command[FASTBOOT_COMMAND_LEN] = {0};
+static char response[FASTBOOT_RESPONSE_LEN] = {0};
+
+static const unsigned short handshake_length = 4;
+static const uchar *handshake = "FB01";
+
+static u16 curr_sport;
+static u16 curr_dport;
+static u32 curr_tcp_seq_num;
+static u32 curr_tcp_ack_num;
+static unsigned int curr_request_len;
+static enum fastboot_tcp_state {
+ FASTBOOT_CLOSED,
+ FASTBOOT_CONNECTED,
+ FASTBOOT_DISCONNECTING
+} state = FASTBOOT_CLOSED;
+
+static void fastboot_tcp_answer(u8 action, unsigned int len)
+{
+ const u32 response_seq_num = curr_tcp_ack_num;
+ const u32 response_ack_num = curr_tcp_seq_num +
+ (curr_request_len > 0 ? curr_request_len : 1);
+
+ net_send_tcp_packet(len, htons(curr_sport), htons(curr_dport),
+ action, response_seq_num, response_ack_num);
+}
+
+static void fastboot_tcp_reset(void)
+{
+ fastboot_tcp_answer(TCP_RST, 0);
+ state = FASTBOOT_CLOSED;
+}
+
+static void fastboot_tcp_send_packet(u8 action, const uchar *data, unsigned int len)
+{
+ uchar *pkt = net_get_async_tx_pkt_buf();
+
+ memset(pkt, '\0', PKTSIZE);
+ pkt += net_eth_hdr_size() + IP_TCP_HDR_SIZE + TCP_TSOPT_SIZE + 2;
+ memcpy(pkt, data, len);
+ fastboot_tcp_answer(action, len);
+ memset(pkt, '\0', PKTSIZE);
+}
+
+static void fastboot_tcp_send_message(const char *message, unsigned int len)
+{
+ __be64 len_be = __cpu_to_be64(len);
+ uchar *pkt = net_get_async_tx_pkt_buf();
+
+ memset(pkt, '\0', PKTSIZE);
+ pkt += net_eth_hdr_size() + IP_TCP_HDR_SIZE + TCP_TSOPT_SIZE + 2;
+ // Put first 8 bytes as a big endian message length
+ memcpy(pkt, &len_be, 8);
+ pkt += 8;
+ memcpy(pkt, message, len);
+ fastboot_tcp_answer(TCP_ACK | TCP_PUSH, len + 8);
+ memset(pkt, '\0', PKTSIZE);
+}
+
+static void fastboot_tcp_handler_ipv4(uchar *pkt, u16 dport,
+ struct in_addr sip, u16 sport,
+ u32 tcp_seq_num, u32 tcp_ack_num,
+ u8 action, unsigned int len)
+{
+ int fastboot_command_id;
+ u64 command_size;
+ u8 tcp_fin = action & TCP_FIN;
+ u8 tcp_push = action & TCP_PUSH;
+
+ curr_sport = sport;
+ curr_dport = dport;
+ curr_tcp_seq_num = tcp_seq_num;
+ curr_tcp_ack_num = tcp_ack_num;
+ curr_request_len = len;
+
+ switch (state) {
+ case FASTBOOT_CLOSED:
+ if (tcp_push) {
+ if (len != handshake_length ||
+ strlen(pkt) != handshake_length ||
+ memcmp(pkt, handshake, handshake_length) != 0) {
+ fastboot_tcp_reset();
+ break;
+ }
+ fastboot_tcp_send_packet(TCP_ACK | TCP_PUSH,
+ handshake, handshake_length);
+ state = FASTBOOT_CONNECTED;
+ }
+ break;
+ case FASTBOOT_CONNECTED:
+ if (tcp_fin) {
+ fastboot_tcp_answer(TCP_FIN | TCP_ACK, 0);
+ state = FASTBOOT_DISCONNECTING;
+ break;
+ }
+ if (tcp_push) {
+ // First 8 bytes is big endian message length
+ command_size = __be64_to_cpu(*(u64 *)pkt);
+ len -= 8;
+ pkt += 8;
+
+ // Only single packet messages are supported ATM
+ if (strlen(pkt) != command_size) {
+ fastboot_tcp_reset();
+ break;
+ }
+ strlcpy(command, pkt, len + 1);
+ fastboot_command_id = fastboot_handle_command(command, response);
+ fastboot_tcp_send_message(response, strlen(response));
+ fastboot_handle_boot(fastboot_command_id,
+ strncmp("OKAY", response, 4) == 0);
+ }
+ break;
+ case FASTBOOT_DISCONNECTING:
+ if (tcp_push)
+ state = FASTBOOT_CLOSED;
+ break;
+ }
+
+ memset(command, 0, FASTBOOT_COMMAND_LEN);
+ memset(response, 0, FASTBOOT_RESPONSE_LEN);
+ curr_sport = 0;
+ curr_dport = 0;
+ curr_tcp_seq_num = 0;
+ curr_tcp_ack_num = 0;
+ curr_request_len = 0;
+}
+
+void fastboot_tcp_start_server(void)
+{
+ printf("Using %s device\n", eth_get_name());
+ printf("Listening for fastboot command on tcp %pI4\n", &net_ip);
+
+ tcp_set_tcp_handler(fastboot_tcp_handler_ipv4);
+}
diff --git a/net/fastboot_udp.c b/net/fastboot_udp.c
new file mode 100644
index 00000000000..d1479510d61
--- /dev/null
+++ b/net/fastboot_udp.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ */
+
+#include <command.h>
+#include <fastboot.h>
+#include <net.h>
+#include <net/fastboot_udp.h>
+#include <linux/printk.h>
+
+enum {
+ FASTBOOT_ERROR = 0,
+ FASTBOOT_QUERY = 1,
+ FASTBOOT_INIT = 2,
+ FASTBOOT_FASTBOOT = 3,
+};
+
+struct __packed fastboot_header {
+ uchar id;
+ uchar flags;
+ unsigned short seq;
+};
+
+#define PACKET_SIZE 1024
+#define DATA_SIZE (PACKET_SIZE - sizeof(struct fastboot_header))
+
+/* Sequence number sent for every packet */
+static unsigned short sequence_number = 1;
+static const unsigned short packet_size = PACKET_SIZE;
+static const unsigned short udp_version = 1;
+
+/* Keep track of last packet for resubmission */
+static uchar last_packet[PACKET_SIZE];
+static unsigned int last_packet_len;
+
+static struct in_addr fastboot_remote_ip;
+/* The UDP port at their end */
+static int fastboot_remote_port;
+/* The UDP port at our end */
+static int fastboot_our_port;
+
+/**
+ * fastboot_udp_send_response() - Send an response into UDP
+ *
+ * @response: Response to send
+ */
+static void fastboot_udp_send_response(const char *response)
+{
+ uchar *packet;
+ uchar *packet_base;
+ int len = 0;
+
+ struct fastboot_header response_header = {
+ .id = FASTBOOT_FASTBOOT,
+ .flags = 0,
+ .seq = htons(sequence_number)
+ };
+ ++sequence_number;
+ packet = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE;
+ packet_base = packet;
+
+ /* Write headers */
+ memcpy(packet, &response_header, sizeof(response_header));
+ packet += sizeof(response_header);
+ /* Write response */
+ memcpy(packet, response, strlen(response));
+ packet += strlen(response);
+
+ len = packet - packet_base;
+
+ /* Save packet for retransmitting */
+ last_packet_len = len;
+ memcpy(last_packet, packet_base, last_packet_len);
+
+ net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip,
+ fastboot_remote_port, fastboot_our_port, len);
+}
+
+/**
+ * fastboot_timed_send_info() - Send INFO packet every 30 seconds
+ *
+ * @msg: String describing the reason for waiting
+ *
+ * Send an INFO packet during long commands based on timer. An INFO packet
+ * is sent if the time is 30 seconds after start. Else, noop.
+ */
+static void fastboot_timed_send_info(const char *msg)
+{
+ static ulong start;
+ char response[FASTBOOT_RESPONSE_LEN] = {0};
+
+ /* Initialize timer */
+ if (start == 0)
+ start = get_timer(0);
+ ulong time = get_timer(start);
+ /* Send INFO packet to host every 30 seconds */
+ if (time >= 30000) {
+ start = get_timer(0);
+ fastboot_response("INFO", response, "%s", msg);
+ fastboot_udp_send_response(response);
+ }
+}
+
+/**
+ * fastboot_send() - Sends a packet in response to received fastboot packet
+ *
+ * @header: Header for response packet
+ * @fastboot_data: Pointer to received fastboot data
+ * @fastboot_data_len: Length of received fastboot data
+ * @retransmit: Nonzero if sending last sent packet
+ */
+static void fastboot_send(struct fastboot_header header, char *fastboot_data,
+ unsigned int fastboot_data_len, uchar retransmit)
+{
+ uchar *packet;
+ uchar *packet_base;
+ int len = 0;
+ const char *error_msg = "An error occurred.";
+ short tmp;
+ struct fastboot_header response_header = header;
+ static char command[FASTBOOT_COMMAND_LEN];
+ static int cmd = -1;
+ static bool pending_command;
+ char response[FASTBOOT_RESPONSE_LEN] = {0};
+
+ /*
+ * We will always be sending some sort of packet, so
+ * cobble together the packet headers now.
+ */
+ packet = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE;
+ packet_base = packet;
+
+ /* Resend last packet */
+ if (retransmit) {
+ memcpy(packet, last_packet, last_packet_len);
+ net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip,
+ fastboot_remote_port, fastboot_our_port,
+ last_packet_len);
+ return;
+ }
+
+ response_header.seq = htons(response_header.seq);
+ memcpy(packet, &response_header, sizeof(response_header));
+ packet += sizeof(response_header);
+
+ switch (header.id) {
+ case FASTBOOT_QUERY:
+ tmp = htons(sequence_number);
+ memcpy(packet, &tmp, sizeof(tmp));
+ packet += sizeof(tmp);
+ break;
+ case FASTBOOT_INIT:
+ tmp = htons(udp_version);
+ memcpy(packet, &tmp, sizeof(tmp));
+ packet += sizeof(tmp);
+ tmp = htons(packet_size);
+ memcpy(packet, &tmp, sizeof(tmp));
+ packet += sizeof(tmp);
+ break;
+ case FASTBOOT_ERROR:
+ memcpy(packet, error_msg, strlen(error_msg));
+ packet += strlen(error_msg);
+ break;
+ case FASTBOOT_FASTBOOT:
+ if (cmd == FASTBOOT_COMMAND_DOWNLOAD) {
+ if (!fastboot_data_len && !fastboot_data_remaining()) {
+ fastboot_data_complete(response);
+ } else {
+ fastboot_data_download(fastboot_data,
+ fastboot_data_len,
+ response);
+ }
+ } else if (!pending_command) {
+ strlcpy(command, fastboot_data,
+ min((size_t)fastboot_data_len + 1,
+ sizeof(command)));
+ pending_command = true;
+ } else {
+ cmd = fastboot_handle_command(command, response);
+ pending_command = false;
+
+ if (!strncmp(FASTBOOT_MULTIRESPONSE_START, response, 4)) {
+ while (1) {
+ /* Call handler to obtain next response */
+ fastboot_multiresponse(cmd, response);
+
+ /*
+ * Send more responses or break to send
+ * final OKAY/FAIL response
+ */
+ if (strncmp("OKAY", response, 4) &&
+ strncmp("FAIL", response, 4))
+ fastboot_udp_send_response(response);
+ else
+ break;
+ }
+ }
+ }
+ /*
+ * Sent some INFO packets, need to update sequence number in
+ * header
+ */
+ if (header.seq != sequence_number) {
+ response_header.seq = htons(sequence_number);
+ memcpy(packet_base, &response_header,
+ sizeof(response_header));
+ }
+ /* Write response to packet */
+ memcpy(packet, response, strlen(response));
+ packet += strlen(response);
+ break;
+ default:
+ pr_err("ID %d not implemented.\n", header.id);
+ return;
+ }
+
+ len = packet - packet_base;
+
+ /* Save packet for retransmitting */
+ last_packet_len = len;
+ memcpy(last_packet, packet_base, last_packet_len);
+
+ net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip,
+ fastboot_remote_port, fastboot_our_port, len);
+
+ fastboot_handle_boot(cmd, strncmp("OKAY", response, 4) == 0);
+
+ if (!strncmp("OKAY", response, 4) || !strncmp("FAIL", response, 4))
+ cmd = -1;
+}
+
+/**
+ * fastboot_handler() - Incoming UDP packet handler.
+ *
+ * @packet: Pointer to incoming UDP packet
+ * @dport: Destination UDP port
+ * @sip: Source IP address
+ * @sport: Source UDP port
+ * @len: Packet length
+ */
+static void fastboot_handler(uchar *packet, unsigned int dport,
+ struct in_addr sip, unsigned int sport,
+ unsigned int len)
+{
+ struct fastboot_header header;
+ char fastboot_data[DATA_SIZE] = {0};
+ unsigned int fastboot_data_len = 0;
+
+ if (dport != fastboot_our_port)
+ return;
+
+ fastboot_remote_ip = sip;
+ fastboot_remote_port = sport;
+
+ if (len < sizeof(struct fastboot_header) || len > PACKET_SIZE)
+ return;
+ memcpy(&header, packet, sizeof(header));
+ header.flags = 0;
+ header.seq = ntohs(header.seq);
+ packet += sizeof(header);
+ len -= sizeof(header);
+
+ switch (header.id) {
+ case FASTBOOT_QUERY:
+ fastboot_send(header, fastboot_data, 0, 0);
+ break;
+ case FASTBOOT_INIT:
+ case FASTBOOT_FASTBOOT:
+ fastboot_data_len = len;
+ if (len > 0)
+ memcpy(fastboot_data, packet, len);
+ if (header.seq == sequence_number) {
+ fastboot_send(header, fastboot_data,
+ fastboot_data_len, 0);
+ sequence_number++;
+ } else if (header.seq == sequence_number - 1) {
+ /* Retransmit last sent packet */
+ fastboot_send(header, fastboot_data,
+ fastboot_data_len, 1);
+ }
+ break;
+ default:
+ pr_err("ID %d not implemented.\n", header.id);
+ header.id = FASTBOOT_ERROR;
+ fastboot_send(header, fastboot_data, 0, 0);
+ break;
+ }
+}
+
+void fastboot_udp_start_server(void)
+{
+ printf("Using %s device\n", eth_get_name());
+ printf("Listening for fastboot command on %pI4\n", &net_ip);
+
+ fastboot_our_port = CONFIG_UDP_FUNCTION_FASTBOOT_PORT;
+
+ if (IS_ENABLED(CONFIG_FASTBOOT_FLASH))
+ fastboot_set_progress_callback(fastboot_timed_send_info);
+
+ net_set_udp_handler(fastboot_handler);
+
+ /* zero out server ether in case the server ip has changed */
+ memset(net_server_ethaddr, 0, 6);
+}
diff --git a/net/link_local.c b/net/link_local.c
new file mode 100644
index 00000000000..179721333ff
--- /dev/null
+++ b/net/link_local.c
@@ -0,0 +1,348 @@
+/*
+ * RFC3927 ZeroConf IPv4 Link-Local addressing
+ * (see <http://www.zeroconf.org/>)
+ *
+ * Copied from BusyBox - networking/zcip.c
+ *
+ * Copyright (C) 2003 by Arthur van Hoff (avh@strangeberry.com)
+ * Copyright (C) 2004 by David Brownell
+ * Copyright (C) 2010 by Joe Hershberger
+ *
+ * Licensed under the GPL v2 or later
+ */
+
+#include <env.h>
+#include <log.h>
+#include <net.h>
+#include <rand.h>
+#include "arp.h"
+#include "net_rand.h"
+
+/* We don't need more than 32 bits of the counter */
+#define MONOTONIC_MS() ((unsigned)get_timer(0) * (1000 / CONFIG_SYS_HZ))
+
+enum {
+/* 169.254.0.0 */
+ LINKLOCAL_ADDR = 0xa9fe0000,
+
+ IN_CLASSB_NET = 0xffff0000,
+ IN_CLASSB_HOST = 0x0000ffff,
+
+/* protocol timeout parameters, specified in seconds */
+ PROBE_WAIT = 1,
+ PROBE_MIN = 1,
+ PROBE_MAX = 2,
+ PROBE_NUM = 3,
+ MAX_CONFLICTS = 10,
+ RATE_LIMIT_INTERVAL = 60,
+ ANNOUNCE_WAIT = 2,
+ ANNOUNCE_NUM = 2,
+ ANNOUNCE_INTERVAL = 2,
+ DEFEND_INTERVAL = 10
+};
+
+/* States during the configuration process. */
+static enum ll_state_t {
+ PROBE = 0,
+ RATE_LIMIT_PROBE,
+ ANNOUNCE,
+ MONITOR,
+ DEFEND,
+ DISABLED
+} state = DISABLED;
+
+static struct in_addr ip;
+static int timeout_ms = -1;
+static unsigned deadline_ms;
+static unsigned conflicts;
+static unsigned nprobes;
+static unsigned nclaims;
+static int ready;
+static unsigned int seed;
+
+static void link_local_timeout(void);
+
+/**
+ * Pick a random link local IP address on 169.254/16, except that
+ * the first and last 256 addresses are reserved.
+ */
+static struct in_addr pick(void)
+{
+ unsigned tmp;
+ struct in_addr ip;
+
+ do {
+ tmp = rand_r(&seed) & IN_CLASSB_HOST;
+ } while (tmp > (IN_CLASSB_HOST - 0x0200));
+ ip.s_addr = htonl((LINKLOCAL_ADDR + 0x0100) + tmp);
+ return ip;
+}
+
+/**
+ * Return milliseconds of random delay, up to "secs" seconds.
+ */
+static inline unsigned random_delay_ms(unsigned secs)
+{
+ return rand_r(&seed) % (secs * 1000);
+}
+
+static void configure_wait(void)
+{
+ if (timeout_ms == -1)
+ return;
+
+ /* poll, being ready to adjust current timeout */
+ if (!timeout_ms)
+ timeout_ms = random_delay_ms(PROBE_WAIT);
+
+ /* set deadline_ms to the point in time when we timeout */
+ deadline_ms = MONOTONIC_MS() + timeout_ms;
+
+ debug_cond(DEBUG_DEV_PKT, "...wait %d %s nprobes=%u, nclaims=%u\n",
+ timeout_ms, eth_get_name(), nprobes, nclaims);
+
+ net_set_timeout_handler(timeout_ms, link_local_timeout);
+}
+
+void link_local_start(void)
+{
+ ip = env_get_ip("llipaddr");
+ if (ip.s_addr != 0 &&
+ (ntohl(ip.s_addr) & IN_CLASSB_NET) != LINKLOCAL_ADDR) {
+ puts("invalid link address");
+ net_set_state(NETLOOP_FAIL);
+ return;
+ }
+ net_netmask.s_addr = htonl(IN_CLASSB_NET);
+
+ seed = seed_mac();
+ if (ip.s_addr == 0)
+ ip = pick();
+
+ state = PROBE;
+ timeout_ms = 0;
+ conflicts = 0;
+ nprobes = 0;
+ nclaims = 0;
+ ready = 0;
+
+ configure_wait();
+}
+
+static void link_local_timeout(void)
+{
+ switch (state) {
+ case PROBE:
+ /* timeouts in the PROBE state mean no conflicting ARP packets
+ have been received, so we can progress through the states */
+ if (nprobes < PROBE_NUM) {
+ struct in_addr zero_ip = {.s_addr = 0};
+
+ nprobes++;
+ debug_cond(DEBUG_LL_STATE, "probe/%u %s@%pI4\n",
+ nprobes, eth_get_name(), &ip);
+ arp_raw_request(zero_ip, net_null_ethaddr, ip);
+ timeout_ms = PROBE_MIN * 1000;
+ timeout_ms += random_delay_ms(PROBE_MAX - PROBE_MIN);
+ } else {
+ /* Switch to announce state */
+ state = ANNOUNCE;
+ nclaims = 0;
+ debug_cond(DEBUG_LL_STATE, "announce/%u %s@%pI4\n",
+ nclaims, eth_get_name(), &ip);
+ arp_raw_request(ip, net_ethaddr, ip);
+ timeout_ms = ANNOUNCE_INTERVAL * 1000;
+ }
+ break;
+ case RATE_LIMIT_PROBE:
+ /* timeouts in the RATE_LIMIT_PROBE state mean no conflicting
+ ARP packets have been received, so we can move immediately
+ to the announce state */
+ state = ANNOUNCE;
+ nclaims = 0;
+ debug_cond(DEBUG_LL_STATE, "announce/%u %s@%pI4\n",
+ nclaims, eth_get_name(), &ip);
+ arp_raw_request(ip, net_ethaddr, ip);
+ timeout_ms = ANNOUNCE_INTERVAL * 1000;
+ break;
+ case ANNOUNCE:
+ /* timeouts in the ANNOUNCE state mean no conflicting ARP
+ packets have been received, so we can progress through
+ the states */
+ if (nclaims < ANNOUNCE_NUM) {
+ nclaims++;
+ debug_cond(DEBUG_LL_STATE, "announce/%u %s@%pI4\n",
+ nclaims, eth_get_name(), &ip);
+ arp_raw_request(ip, net_ethaddr, ip);
+ timeout_ms = ANNOUNCE_INTERVAL * 1000;
+ } else {
+ /* Switch to monitor state */
+ state = MONITOR;
+ printf("Successfully assigned %pI4\n", &ip);
+ net_copy_ip(&net_ip, &ip);
+ ready = 1;
+ conflicts = 0;
+ timeout_ms = -1;
+ /* Never timeout in the monitor state */
+ net_set_timeout_handler(0, NULL);
+
+ /* NOTE: all other exit paths should deconfig ... */
+ net_set_state(NETLOOP_SUCCESS);
+ return;
+ }
+ break;
+ case DEFEND:
+ /* We won! No ARP replies, so just go back to monitor */
+ state = MONITOR;
+ timeout_ms = -1;
+ conflicts = 0;
+ break;
+ default:
+ /* Invalid, should never happen. Restart the whole protocol */
+ state = PROBE;
+ ip = pick();
+ timeout_ms = 0;
+ nprobes = 0;
+ nclaims = 0;
+ break;
+ }
+ configure_wait();
+}
+
+void link_local_receive_arp(struct arp_hdr *arp, int len)
+{
+ int source_ip_conflict;
+ int target_ip_conflict;
+ struct in_addr null_ip = {.s_addr = 0};
+
+ if (state == DISABLED)
+ return;
+
+ /* We need to adjust the timeout in case we didn't receive a
+ conflicting packet. */
+ if (timeout_ms > 0) {
+ unsigned diff = deadline_ms - MONOTONIC_MS();
+ if ((int)(diff) < 0) {
+ /* Current time is greater than the expected timeout
+ time. This should never happen */
+ debug_cond(DEBUG_LL_STATE,
+ "missed an expected timeout\n");
+ timeout_ms = 0;
+ } else {
+ debug_cond(DEBUG_INT_STATE, "adjusting timeout\n");
+ timeout_ms = diff | 1; /* never 0 */
+ }
+ }
+#if 0
+ /* XXX Don't bother with ethernet link just yet */
+ if ((fds[0].revents & POLLIN) == 0) {
+ if (fds[0].revents & POLLERR) {
+ /*
+ * FIXME: links routinely go down;
+ */
+ bb_error_msg("iface %s is down", eth_get_name());
+ if (ready)
+ run(argv, "deconfig", &ip);
+ return EXIT_FAILURE;
+ }
+ continue;
+ }
+#endif
+
+ debug_cond(DEBUG_INT_STATE, "%s recv arp type=%d, op=%d,\n",
+ eth_get_name(), ntohs(arp->ar_pro),
+ ntohs(arp->ar_op));
+ debug_cond(DEBUG_INT_STATE, "\tsource=%pM %pI4\n",
+ &arp->ar_sha,
+ &arp->ar_spa);
+ debug_cond(DEBUG_INT_STATE, "\ttarget=%pM %pI4\n",
+ &arp->ar_tha,
+ &arp->ar_tpa);
+
+ if (arp->ar_op != htons(ARPOP_REQUEST) &&
+ arp->ar_op != htons(ARPOP_REPLY)) {
+ configure_wait();
+ return;
+ }
+
+ source_ip_conflict = 0;
+ target_ip_conflict = 0;
+
+ if (memcmp(&arp->ar_spa, &ip, ARP_PLEN) == 0 &&
+ memcmp(&arp->ar_sha, net_ethaddr, ARP_HLEN) != 0)
+ source_ip_conflict = 1;
+
+ /*
+ * According to RFC 3927, section 2.2.1:
+ * Check if packet is an ARP probe by checking for a null source IP
+ * then check that target IP is equal to ours and source hw addr
+ * is not equal to ours. This condition should cause a conflict only
+ * during probe.
+ */
+ if (arp->ar_op == htons(ARPOP_REQUEST) &&
+ memcmp(&arp->ar_spa, &null_ip, ARP_PLEN) == 0 &&
+ memcmp(&arp->ar_tpa, &ip, ARP_PLEN) == 0 &&
+ memcmp(&arp->ar_sha, net_ethaddr, ARP_HLEN) != 0) {
+ target_ip_conflict = 1;
+ }
+
+ debug_cond(DEBUG_NET_PKT,
+ "state = %d, source ip conflict = %d, target ip conflict = "
+ "%d\n", state, source_ip_conflict, target_ip_conflict);
+ switch (state) {
+ case PROBE:
+ case ANNOUNCE:
+ /* When probing or announcing, check for source IP conflicts
+ and other hosts doing ARP probes (target IP conflicts). */
+ if (source_ip_conflict || target_ip_conflict) {
+ conflicts++;
+ state = PROBE;
+ if (conflicts >= MAX_CONFLICTS) {
+ debug("%s ratelimit\n", eth_get_name());
+ timeout_ms = RATE_LIMIT_INTERVAL * 1000;
+ state = RATE_LIMIT_PROBE;
+ }
+
+ /* restart the whole protocol */
+ ip = pick();
+ timeout_ms = 0;
+ nprobes = 0;
+ nclaims = 0;
+ }
+ break;
+ case MONITOR:
+ /* If a conflict, we try to defend with a single ARP probe */
+ if (source_ip_conflict) {
+ debug("monitor conflict -- defending\n");
+ state = DEFEND;
+ timeout_ms = DEFEND_INTERVAL * 1000;
+ arp_raw_request(ip, net_ethaddr, ip);
+ }
+ break;
+ case DEFEND:
+ /* Well, we tried. Start over (on conflict) */
+ if (source_ip_conflict) {
+ state = PROBE;
+ debug("defend conflict -- starting over\n");
+ ready = 0;
+ net_ip.s_addr = 0;
+
+ /* restart the whole protocol */
+ ip = pick();
+ timeout_ms = 0;
+ nprobes = 0;
+ nclaims = 0;
+ }
+ break;
+ default:
+ /* Invalid, should never happen. Restart the whole protocol */
+ debug("invalid state -- starting over\n");
+ state = PROBE;
+ ip = pick();
+ timeout_ms = 0;
+ nprobes = 0;
+ nclaims = 0;
+ break;
+ }
+ configure_wait();
+}
diff --git a/net/link_local.h b/net/link_local.h
new file mode 100644
index 00000000000..d8701255142
--- /dev/null
+++ b/net/link_local.h
@@ -0,0 +1,21 @@
+/*
+ * RFC3927 ZeroConf IPv4 Link-Local addressing
+ * (see <http://www.zeroconf.org/>)
+ *
+ * Copied from BusyBox - networking/zcip.c
+ *
+ * Copyright (C) 2003 by Arthur van Hoff (avh@strangeberry.com)
+ * Copyright (C) 2004 by David Brownell
+ *
+ * Licensed under the GPL v2 or later
+ */
+
+#ifndef __LINK_LOCAL_H__
+#define __LINK_LOCAL_H__
+
+struct arp_hdr;
+
+void link_local_receive_arp(struct arp_hdr *arp, int len);
+void link_local_start(void);
+
+#endif /* __LINK_LOCAL_H__ */
diff --git a/net/mdio-mux-uclass.c b/net/mdio-mux-uclass.c
new file mode 100644
index 00000000000..ee188b504d1
--- /dev/null
+++ b/net/mdio-mux-uclass.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2019
+ * Alex Marginean, NXP
+ */
+
+#include <dm.h>
+#include <log.h>
+#include <miiphy.h>
+#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
+#include <dm/lists.h>
+
+#define MDIO_MUX_CHILD_DRV_NAME "mdio-mux-bus-drv"
+
+/**
+ * struct mdio_mux_perdev_priv - Per-device class data for MDIO MUX DM
+ *
+ * @parent_mdio: Parent DM MDIO device, this is called for actual MDIO I/O after
+ * setting up the mux. Typically this is a real MDIO device,
+ * unless there are cascaded muxes.
+ * @selected: Current child bus selection. Defaults to -1
+ */
+struct mdio_mux_perdev_priv {
+ struct udevice *mdio_parent;
+ int selected;
+};
+
+/*
+ * This source file uses three types of devices, as follows:
+ * - mux is the hardware MDIO MUX which selects between the existing child MDIO
+ * buses, this is the device relevant for MDIO MUX class of drivers.
+ * - ch is a child MDIO bus, this is just a representation of a mux selection,
+ * not a real piece of hardware.
+ * - mdio_parent is the actual MDIO bus called to perform reads/writes after
+ * the MUX is configured. Typically this is a real MDIO device, unless there
+ * are cascaded muxes.
+ */
+
+/**
+ * struct mdio_mux_ch_data - Per-device data for child MDIOs
+ *
+ * @sel: Selection value used by the MDIO MUX to access this child MDIO bus
+ */
+struct mdio_mux_ch_data {
+ int sel;
+};
+
+static struct udevice *mmux_get_parent_mdio(struct udevice *mux)
+{
+ struct mdio_mux_perdev_priv *pdata = dev_get_uclass_priv(mux);
+
+ return pdata->mdio_parent;
+}
+
+/* call driver select function before performing MDIO r/w */
+static int mmux_change_sel(struct udevice *ch, bool sel)
+{
+ struct udevice *mux = ch->parent;
+ struct mdio_mux_perdev_priv *priv = dev_get_uclass_priv(mux);
+ struct mdio_mux_ops *ops = mdio_mux_get_ops(mux);
+ struct mdio_mux_ch_data *ch_data = dev_get_parent_plat(ch);
+ int err = 0;
+
+ if (sel) {
+ err = ops->select(mux, priv->selected, ch_data->sel);
+ if (err)
+ return err;
+
+ priv->selected = ch_data->sel;
+ } else {
+ if (ops->deselect) {
+ ops->deselect(mux, ch_data->sel);
+ priv->selected = MDIO_MUX_SELECT_NONE;
+ }
+ }
+
+ return 0;
+}
+
+/* Read wrapper, sets up the mux before issuing a read on parent MDIO bus */
+static int mmux_read(struct udevice *ch, int addr, int devad,
+ int reg)
+{
+ struct udevice *mux = ch->parent;
+ struct udevice *parent_mdio = mmux_get_parent_mdio(mux);
+ int err;
+
+ err = mmux_change_sel(ch, true);
+ if (err)
+ return err;
+
+ err = dm_mdio_read(parent_mdio, addr, devad, reg);
+ mmux_change_sel(ch, false);
+
+ return err;
+}
+
+/* Write wrapper, sets up the mux before issuing a write on parent MDIO bus */
+static int mmux_write(struct udevice *ch, int addr, int devad,
+ int reg, u16 val)
+{
+ struct udevice *mux = ch->parent;
+ struct udevice *parent_mdio = mmux_get_parent_mdio(mux);
+ int err;
+
+ err = mmux_change_sel(ch, true);
+ if (err)
+ return err;
+
+ err = dm_mdio_write(parent_mdio, addr, devad, reg, val);
+ mmux_change_sel(ch, false);
+
+ return err;
+}
+
+/* Reset wrapper, sets up the mux before issuing a reset on parent MDIO bus */
+static int mmux_reset(struct udevice *ch)
+{
+ struct udevice *mux = ch->parent;
+ struct udevice *parent_mdio = mmux_get_parent_mdio(mux);
+ int err;
+
+ /* reset is optional, if it's not implemented just exit */
+ if (!mdio_get_ops(parent_mdio)->reset)
+ return 0;
+
+ err = mmux_change_sel(ch, true);
+ if (err)
+ return err;
+
+ err = dm_mdio_reset(parent_mdio);
+ mmux_change_sel(ch, false);
+
+ return err;
+}
+
+/* Picks up the mux selection value for each child */
+static int dm_mdio_mux_child_post_bind(struct udevice *ch)
+{
+ struct mdio_mux_ch_data *ch_data = dev_get_parent_plat(ch);
+
+ ch_data->sel = dev_read_u32_default(ch, "reg", MDIO_MUX_SELECT_NONE);
+
+ if (ch_data->sel == MDIO_MUX_SELECT_NONE)
+ return -EINVAL;
+
+ return 0;
+}
+
+/* Explicitly bind child MDIOs after binding the mux */
+static int dm_mdio_mux_post_bind(struct udevice *mux)
+{
+ ofnode ch_node;
+ int err, first_err = 0;
+
+ if (!dev_has_ofnode(mux)) {
+ debug("%s: no mux node found, no child MDIO busses set up\n",
+ __func__);
+ return 0;
+ }
+
+ /*
+ * we're going by Linux bindings so the child nodes do not have
+ * compatible strings. We're going through them here and binding to
+ * them.
+ */
+ dev_for_each_subnode(ch_node, mux) {
+ struct udevice *ch_dev;
+ const char *ch_name;
+
+ ch_name = ofnode_get_name(ch_node);
+
+ err = device_bind_driver_to_node(mux, MDIO_MUX_CHILD_DRV_NAME,
+ ch_name, ch_node, &ch_dev);
+ /* try to bind all, but keep 1st error */
+ if (err && !first_err)
+ first_err = err;
+ }
+
+ return first_err;
+}
+
+/* Get a reference to the parent MDIO bus, it should be bound by now */
+static int dm_mdio_mux_post_probe(struct udevice *mux)
+{
+ struct mdio_mux_perdev_priv *priv = dev_get_uclass_priv(mux);
+ int err;
+
+ priv->selected = MDIO_MUX_SELECT_NONE;
+
+ /* pick up mdio parent from device tree */
+ err = uclass_get_device_by_phandle(UCLASS_MDIO, mux, "mdio-parent-bus",
+ &priv->mdio_parent);
+ if (err) {
+ debug("%s: didn't find mdio-parent-bus\n", __func__);
+ return err;
+ }
+
+ return 0;
+}
+
+const struct mdio_ops mmux_child_mdio_ops = {
+ .read = mmux_read,
+ .write = mmux_write,
+ .reset = mmux_reset,
+};
+
+/* MDIO class driver used for MUX child MDIO buses */
+U_BOOT_DRIVER(mdio_mux_child) = {
+ .name = MDIO_MUX_CHILD_DRV_NAME,
+ .id = UCLASS_MDIO,
+ .ops = &mmux_child_mdio_ops,
+};
+
+UCLASS_DRIVER(mdio_mux) = {
+ .id = UCLASS_MDIO_MUX,
+ .name = "mdio-mux",
+ .child_post_bind = dm_mdio_mux_child_post_bind,
+ .post_bind = dm_mdio_mux_post_bind,
+ .post_probe = dm_mdio_mux_post_probe,
+ .per_device_auto = sizeof(struct mdio_mux_perdev_priv),
+ .per_child_plat_auto = sizeof(struct mdio_mux_ch_data),
+};
diff --git a/net/mdio-uclass.c b/net/mdio-uclass.c
new file mode 100644
index 00000000000..4f052ae432c
--- /dev/null
+++ b/net/mdio-uclass.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2019
+ * Alex Marginean, NXP
+ */
+
+#include <dm.h>
+#include <dm/lists.h>
+#include <eth_phy.h>
+#include <log.h>
+#include <malloc.h>
+#include <miiphy.h>
+#include <dm/device-internal.h>
+#include <dm/device_compat.h>
+#include <dm/of_extra.h>
+#include <dm/uclass-internal.h>
+#include <linux/compat.h>
+#include <linux/delay.h>
+
+#define DEFAULT_GPIO_RESET_DELAY 10 /* in microseconds */
+
+void dm_mdio_probe_devices(void)
+{
+ struct udevice *it;
+ struct uclass *uc;
+
+ uclass_get(UCLASS_MDIO, &uc);
+ uclass_foreach_dev(it, uc) {
+ device_probe(it);
+ }
+}
+
+static int dm_mdio_post_bind(struct udevice *dev)
+{
+ const char *dt_name;
+
+ /* set a custom name for the MDIO device, if present in DT */
+ if (dev_has_ofnode(dev)) {
+ dt_name = dev_read_string(dev, "device-name");
+ if (dt_name) {
+ debug("renaming dev %s to %s\n", dev->name, dt_name);
+ device_set_name(dev, dt_name);
+ }
+ }
+
+ /*
+ * MDIO command doesn't like spaces in names, don't allow them to keep
+ * it happy
+ */
+ if (strchr(dev->name, ' ')) {
+ debug("\nError: MDIO device name \"%s\" has a space!\n",
+ dev->name);
+ return -EINVAL;
+ }
+
+#if CONFIG_IS_ENABLED(OF_REAL)
+ return dm_scan_fdt_dev(dev);
+#else
+ return 0;
+#endif
+}
+
+int dm_mdio_read(struct udevice *mdio_dev, int addr, int devad, int reg)
+{
+ struct mdio_ops *ops = mdio_get_ops(mdio_dev);
+
+ if (!ops->read)
+ return -ENOSYS;
+
+ return ops->read(mdio_dev, addr, devad, reg);
+}
+
+int dm_mdio_write(struct udevice *mdio_dev, int addr, int devad, int reg,
+ u16 val)
+{
+ struct mdio_ops *ops = mdio_get_ops(mdio_dev);
+
+ if (!ops->write)
+ return -ENOSYS;
+
+ return ops->write(mdio_dev, addr, devad, reg, val);
+}
+
+int dm_mdio_reset(struct udevice *mdio_dev)
+{
+ struct mdio_ops *ops = mdio_get_ops(mdio_dev);
+ struct mdio_perdev_priv *pdata = dev_get_uclass_priv(mdio_dev);
+ struct mii_dev *mii_bus = pdata->mii_bus;
+
+ if (CONFIG_IS_ENABLED(DM_GPIO) && dm_gpio_is_valid(&mii_bus->reset_gpiod)) {
+ dm_gpio_set_value(&mii_bus->reset_gpiod, 1);
+ udelay(mii_bus->reset_delay_us);
+ dm_gpio_set_value(&mii_bus->reset_gpiod, 0);
+ if (mii_bus->reset_post_delay_us > 0)
+ udelay(mii_bus->reset_post_delay_us);
+ }
+
+ if (!ops->reset)
+ return 0;
+
+ return ops->reset(mdio_dev);
+}
+
+/*
+ * Following read/write/reset functions are registered with legacy MII code.
+ * These are called for PHY operations by upper layers and we further call the
+ * DM MDIO driver functions.
+ */
+static int mdio_read(struct mii_dev *mii_bus, int addr, int devad, int reg)
+{
+ return dm_mdio_read(mii_bus->priv, addr, devad, reg);
+}
+
+static int mdio_write(struct mii_dev *mii_bus, int addr, int devad, int reg,
+ u16 val)
+{
+ return dm_mdio_write(mii_bus->priv, addr, devad, reg, val);
+}
+
+static int mdio_reset(struct mii_dev *mii_bus)
+{
+ return dm_mdio_reset(mii_bus->priv);
+}
+
+static int mdio_bind_phy_nodes(struct udevice *mdio_dev)
+{
+ ofnode mdio_node, phy_node;
+ struct udevice *phy_dev;
+ const char *node_name;
+ int ret;
+
+ mdio_node = dev_ofnode(mdio_dev);
+ if (!ofnode_valid(mdio_node)) {
+ dev_dbg(mdio_dev, "invalid ofnode for mdio_dev\n");
+ return -ENXIO;
+ }
+
+ ofnode_for_each_subnode(phy_node, mdio_node) {
+ node_name = ofnode_get_name(phy_node);
+ dev_dbg(mdio_dev, "* Found child node: '%s'\n", node_name);
+ ret = device_bind_driver_to_node(mdio_dev,
+ "eth_phy_generic_drv",
+ node_name, phy_node, &phy_dev);
+ if (ret) {
+ dev_dbg(mdio_dev, " - Eth phy binding error: %d\n", ret);
+ continue;
+ }
+
+ dev_dbg(mdio_dev, " - bound phy device: '%s'\n", node_name);
+ ret = device_probe(phy_dev);
+ if (ret) {
+ dev_dbg(mdio_dev, "Device '%s' probe failed\n", phy_dev->name);
+ device_unbind(phy_dev);
+ continue;
+ }
+ }
+
+ return 0;
+}
+
+static int dm_mdio_post_probe(struct udevice *dev)
+{
+ struct mdio_perdev_priv *pdata = dev_get_uclass_priv(dev);
+ struct mii_dev *mii_bus;
+ int ret;
+
+ mii_bus = mdio_alloc();
+ if (!mii_bus) {
+ dev_err(dev, "couldn't allocate mii_bus\n");
+ return -ENOMEM;
+ }
+ pdata->mii_bus = mii_bus;
+ pdata->mii_bus->read = mdio_read;
+ pdata->mii_bus->write = mdio_write;
+ pdata->mii_bus->reset = mdio_reset;
+ pdata->mii_bus->priv = dev;
+ strlcpy(pdata->mii_bus->name, dev->name, MDIO_NAME_LEN);
+
+ if (IS_ENABLED(CONFIG_DM_GPIO)) {
+ /* Get bus level PHY reset GPIO details */
+ mii_bus->reset_delay_us = dev_read_u32_default(dev, "reset-delay-us",
+ DEFAULT_GPIO_RESET_DELAY);
+ mii_bus->reset_post_delay_us = dev_read_u32_default(dev,
+ "reset-post-delay-us",
+ 0);
+ ret = gpio_request_by_name(dev, "reset-gpios", 0, &mii_bus->reset_gpiod,
+ GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);
+ if (ret && ret != -ENOENT) {
+ dev_err(dev, "couldn't get reset-gpios: %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (CONFIG_IS_ENABLED(DM_ETH_PHY))
+ mdio_bind_phy_nodes(dev);
+
+ return mdio_register(pdata->mii_bus);
+}
+
+static int dm_mdio_pre_remove(struct udevice *dev)
+{
+ struct mdio_perdev_priv *pdata = dev_get_uclass_priv(dev);
+
+ dm_mdio_reset(dev);
+ mdio_unregister(pdata->mii_bus);
+ mdio_free(pdata->mii_bus);
+
+ return 0;
+}
+
+struct phy_device *dm_phy_find_by_ofnode(ofnode phynode)
+{
+ struct mdio_perdev_priv *pdata;
+ struct udevice *mdiodev;
+ u32 phy_addr;
+
+ if (ofnode_read_u32(phynode, "reg", &phy_addr))
+ return NULL;
+
+ if (uclass_get_device_by_ofnode(UCLASS_MDIO,
+ ofnode_get_parent(phynode),
+ &mdiodev))
+ return NULL;
+
+ if (device_probe(mdiodev))
+ return NULL;
+
+ pdata = dev_get_uclass_priv(mdiodev);
+
+ return phy_find_by_mask(pdata->mii_bus, BIT(phy_addr));
+}
+
+struct phy_device *dm_mdio_phy_connect(struct udevice *mdiodev, int phyaddr,
+ struct udevice *ethdev,
+ phy_interface_t interface)
+{
+ struct mdio_perdev_priv *pdata = dev_get_uclass_priv(mdiodev);
+
+ if (device_probe(mdiodev))
+ return NULL;
+
+ return phy_connect(pdata->mii_bus, phyaddr, ethdev, interface);
+}
+
+static struct phy_device *dm_eth_connect_phy_handle(struct udevice *ethdev,
+ phy_interface_t interface)
+{
+ u32 phy_addr;
+ struct udevice *mdiodev;
+ struct phy_device *phy;
+ ofnode phynode;
+
+ if (IS_ENABLED(CONFIG_PHY_FIXED) &&
+ ofnode_phy_is_fixed_link(dev_ofnode(ethdev), &phynode)) {
+ phy = phy_connect(NULL, 0, ethdev, interface);
+ goto out;
+ }
+
+ phynode = dev_get_phy_node(ethdev);
+ if (!ofnode_valid(phynode)) {
+ dev_dbg(ethdev, "can't find PHY node\n");
+ return NULL;
+ }
+
+ /*
+ * reading 'reg' directly should be fine. This is a PHY node, the
+ * address is always size 1 and requires no translation
+ */
+ if (ofnode_read_u32(phynode, "reg", &phy_addr)) {
+ dev_dbg(ethdev, "missing reg property in phy node\n");
+ return NULL;
+ }
+
+ if (uclass_get_device_by_ofnode(UCLASS_MDIO,
+ ofnode_get_parent(phynode),
+ &mdiodev)) {
+ dev_dbg(ethdev, "can't find MDIO bus for node %s\n",
+ ofnode_get_name(ofnode_get_parent(phynode)));
+ return NULL;
+ }
+
+ phy = dm_mdio_phy_connect(mdiodev, phy_addr, ethdev, interface);
+
+out:
+ if (phy)
+ phy->node = phynode;
+
+ return phy;
+}
+
+/* Connect to a PHY linked in eth DT node */
+struct phy_device *dm_eth_phy_connect(struct udevice *ethdev)
+{
+ phy_interface_t interface;
+ struct phy_device *phy;
+
+ if (!dev_has_ofnode(ethdev)) {
+ debug("%s: supplied eth dev has no DT node!\n", ethdev->name);
+ return NULL;
+ }
+
+ interface = dev_read_phy_mode(ethdev);
+ if (interface == PHY_INTERFACE_MODE_NA)
+ dev_dbg(ethdev, "can't find interface mode, default to NA\n");
+
+ phy = dm_eth_connect_phy_handle(ethdev, interface);
+
+ if (!phy)
+ return NULL;
+
+ phy->interface = interface;
+
+ return phy;
+}
+
+UCLASS_DRIVER(mdio) = {
+ .id = UCLASS_MDIO,
+ .name = "mdio",
+ .post_bind = dm_mdio_post_bind,
+ .post_probe = dm_mdio_post_probe,
+ .pre_remove = dm_mdio_pre_remove,
+ .per_device_auto = sizeof(struct mdio_perdev_priv),
+};
diff --git a/net/ndisc.c b/net/ndisc.c
new file mode 100644
index 00000000000..d417c5987ac
--- /dev/null
+++ b/net/ndisc.c
@@ -0,0 +1,515 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Allied Telesis Labs NZ
+ * Chris Packham, <judge.packham@gmail.com>
+ *
+ * Copyright (C) 2022 YADRO
+ * Viacheslav Mitrofanov <v.v.mitrofanov@yadro.com>
+ */
+
+/* Neighbour Discovery for IPv6 */
+
+#include <net.h>
+#include <net6.h>
+#include <ndisc.h>
+#include <stdlib.h>
+#include <linux/delay.h>
+
+/* IPv6 destination address of packet waiting for ND */
+struct in6_addr net_nd_sol_packet_ip6 = ZERO_IPV6_ADDR;
+/* IPv6 address we are expecting ND advert from */
+static struct in6_addr net_nd_rep_packet_ip6 = ZERO_IPV6_ADDR;
+/* MAC destination address of packet waiting for ND */
+uchar *net_nd_packet_mac;
+/* pointer to packet waiting to be transmitted after ND is resolved */
+uchar *net_nd_tx_packet;
+static uchar net_nd_packet_buf[PKTSIZE_ALIGN + PKTALIGN];
+/* size of packet waiting to be transmitted */
+int net_nd_tx_packet_size;
+/* the timer for ND resolution */
+ulong net_nd_timer_start;
+/* the number of requests we have sent so far */
+int net_nd_try;
+struct in6_addr all_routers = ALL_ROUTERS_MULT_ADDR;
+
+#define MAX_RTR_SOLICITATIONS 3
+/* The maximum time to delay sending the first router solicitation message. */
+#define MAX_SOLICITATION_DELAY 1 // 1 second
+/* The time to wait before sending the next router solicitation message. */
+#define RTR_SOLICITATION_INTERVAL 4000 // 4 seconds
+
+#define IP6_NDISC_OPT_SPACE(len) (((len) + 2 + 7) & ~7)
+
+/**
+ * ndisc_insert_option() - Insert an option into a neighbor discovery packet
+ *
+ * @opt: pointer to the option element of the neighbor discovery packet
+ * @type: option type to insert
+ * @data: option data to insert
+ * @len: data length
+ * Return: the number of bytes inserted (which may be >= len)
+ */
+static int ndisc_insert_option(__u8 *opt, int type, u8 *data, int len)
+{
+ int space = IP6_NDISC_OPT_SPACE(len);
+
+ opt[0] = type;
+ opt[1] = space >> 3;
+ memcpy(&opt[2], data, len);
+ len += 2;
+
+ /* fill the remainder with 0 */
+ if (space - len > 0)
+ memset(&opt[len], '\0', space - len);
+
+ return space;
+}
+
+/**
+ * ndisc_extract_enetaddr() - Extract the Ethernet address from a ND packet
+ *
+ * Note that the link layer address could be anything but the only networking
+ * media that u-boot supports is Ethernet so we assume we're extracting a 6
+ * byte Ethernet MAC address.
+ *
+ * @ndisc: pointer to ND packet
+ * @enetaddr: extracted MAC addr
+ */
+static void ndisc_extract_enetaddr(struct nd_msg *ndisc, uchar enetaddr[6])
+{
+ memcpy(enetaddr, &ndisc->opt[2], 6);
+}
+
+/**
+ * ndisc_has_option() - Check if the ND packet has the specified option set
+ *
+ * @ip6: pointer to IPv6 header
+ * @type: option type to check
+ * Return: 1 if ND has that option, 0 therwise
+ */
+static int ndisc_has_option(struct ip6_hdr *ip6, __u8 type)
+{
+ struct nd_msg *ndisc = (struct nd_msg *)(((uchar *)ip6) + IP6_HDR_SIZE);
+
+ if (ip6->payload_len <= sizeof(struct icmp6hdr))
+ return 0;
+
+ return ndisc->opt[0] == type;
+}
+
+static void ip6_send_ns(struct in6_addr *neigh_addr)
+{
+ struct in6_addr dst_adr;
+ unsigned char enetaddr[6];
+ struct nd_msg *msg;
+ __u16 len;
+ uchar *pkt;
+ unsigned short csum;
+ unsigned int pcsum;
+
+ debug("sending neighbor solicitation for %pI6c our address %pI6c\n",
+ neigh_addr, &net_link_local_ip6);
+
+ /* calculate src, dest IPv6 addr and dest Eth addr */
+ ip6_make_snma(&dst_adr, neigh_addr);
+ ip6_make_mult_ethdstaddr(enetaddr, &dst_adr);
+ len = sizeof(struct icmp6hdr) + IN6ADDRSZ +
+ IP6_NDISC_OPT_SPACE(INETHADDRSZ);
+
+ pkt = (uchar *)net_tx_packet;
+ pkt += net_set_ether(pkt, enetaddr, PROT_IP6);
+ pkt += ip6_add_hdr(pkt, &net_link_local_ip6, &dst_adr, PROT_ICMPV6,
+ IPV6_NDISC_HOPLIMIT, len);
+
+ /* ICMPv6 - NS */
+ msg = (struct nd_msg *)pkt;
+ msg->icmph.icmp6_type = IPV6_NDISC_NEIGHBOUR_SOLICITATION;
+ msg->icmph.icmp6_code = 0;
+ memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16));
+ memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32));
+
+ /* Set the target address and llsaddr option */
+ net_copy_ip6(&msg->target, neigh_addr);
+ ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr,
+ INETHADDRSZ);
+
+ /* checksum */
+ pcsum = csum_partial((__u8 *)msg, len, 0);
+ csum = csum_ipv6_magic(&net_link_local_ip6, &dst_adr,
+ len, PROT_ICMPV6, pcsum);
+ msg->icmph.icmp6_cksum = csum;
+ pkt += len;
+
+ /* send it! */
+ net_send_packet(net_tx_packet, (pkt - net_tx_packet));
+}
+
+/*
+ * ip6_send_rs() - Send IPv6 Router Solicitation Message.
+ *
+ * A router solicitation is sent to discover a router. RS message creation is
+ * based on RFC 4861 section 4.1. Router Solicitation Message Format.
+ */
+void ip6_send_rs(void)
+{
+ unsigned char enetaddr[6];
+ struct rs_msg *msg;
+ __u16 icmp_len;
+ uchar *pkt;
+ unsigned short csum;
+ unsigned int pcsum;
+ static unsigned int retry_count;
+
+ if (!ip6_is_unspecified_addr(&net_gateway6) &&
+ net_prefix_length != 0) {
+ net_set_state(NETLOOP_SUCCESS);
+ return;
+ } else if (retry_count >= MAX_RTR_SOLICITATIONS) {
+ net_set_state(NETLOOP_FAIL);
+ net_set_timeout_handler(0, NULL);
+ retry_count = 0;
+ return;
+ }
+
+ printf("ROUTER SOLICITATION %d\n", retry_count + 1);
+
+ ip6_make_mult_ethdstaddr(enetaddr, &all_routers);
+ /*
+ * ICMP length is the size of ICMP header (8) + one option (8) = 16.
+ * The option is 2 bytes of type and length + 6 bytes for MAC.
+ */
+ icmp_len = sizeof(struct icmp6hdr) + IP6_NDISC_OPT_SPACE(INETHADDRSZ);
+
+ pkt = (uchar *)net_tx_packet;
+ pkt += net_set_ether(pkt, enetaddr, PROT_IP6);
+ pkt += ip6_add_hdr(pkt, &net_link_local_ip6, &all_routers, PROT_ICMPV6,
+ IPV6_NDISC_HOPLIMIT, icmp_len);
+
+ /* ICMPv6 - RS */
+ msg = (struct rs_msg *)pkt;
+ msg->icmph.icmp6_type = IPV6_NDISC_ROUTER_SOLICITATION;
+ msg->icmph.icmp6_code = 0;
+ memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16));
+ memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32));
+
+ /* Set the llsaddr option */
+ ndisc_insert_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, net_ethaddr,
+ INETHADDRSZ);
+
+ /* checksum */
+ pcsum = csum_partial((__u8 *)msg, icmp_len, 0);
+ csum = csum_ipv6_magic(&net_link_local_ip6, &all_routers,
+ icmp_len, PROT_ICMPV6, pcsum);
+ msg->icmph.icmp6_cksum = csum;
+ pkt += icmp_len;
+
+ /* Wait up to 1 second if it is the first try to get the RA */
+ if (retry_count == 0)
+ udelay(((unsigned int)rand() % 1000000) * MAX_SOLICITATION_DELAY);
+
+ /* send it! */
+ net_send_packet(net_tx_packet, (pkt - net_tx_packet));
+
+ retry_count++;
+ net_set_timeout_handler(RTR_SOLICITATION_INTERVAL, ip6_send_rs);
+}
+
+static void
+ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr,
+ struct in6_addr *target)
+{
+ struct nd_msg *msg;
+ __u16 len;
+ uchar *pkt;
+ unsigned short csum;
+
+ debug("sending neighbor advertisement for %pI6c to %pI6c (%pM)\n",
+ target, neigh_addr, eth_dst_addr);
+
+ len = sizeof(struct icmp6hdr) + IN6ADDRSZ +
+ IP6_NDISC_OPT_SPACE(INETHADDRSZ);
+
+ pkt = (uchar *)net_tx_packet;
+ pkt += net_set_ether(pkt, eth_dst_addr, PROT_IP6);
+ pkt += ip6_add_hdr(pkt, &net_link_local_ip6, neigh_addr,
+ PROT_ICMPV6, IPV6_NDISC_HOPLIMIT, len);
+
+ /* ICMPv6 - NA */
+ msg = (struct nd_msg *)pkt;
+ msg->icmph.icmp6_type = IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT;
+ msg->icmph.icmp6_code = 0;
+ memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16));
+ memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32));
+ msg->icmph.icmp6_dataun.u_nd_advt.solicited = 1;
+ msg->icmph.icmp6_dataun.u_nd_advt.override = 1;
+ /* Set the target address and lltargetaddr option */
+ net_copy_ip6(&msg->target, target);
+ ndisc_insert_option(msg->opt, ND_OPT_TARGET_LL_ADDR, net_ethaddr,
+ INETHADDRSZ);
+
+ /* checksum */
+ csum = csum_ipv6_magic(&net_link_local_ip6,
+ neigh_addr, len, PROT_ICMPV6,
+ csum_partial((__u8 *)msg, len, 0));
+ msg->icmph.icmp6_cksum = csum;
+ pkt += len;
+
+ /* send it! */
+ net_send_packet(net_tx_packet, (pkt - net_tx_packet));
+}
+
+void ndisc_request(void)
+{
+ if (!ip6_addr_in_subnet(&net_ip6, &net_nd_sol_packet_ip6,
+ net_prefix_length)) {
+ if (ip6_is_unspecified_addr(&net_gateway6)) {
+ puts("## Warning: gatewayip6 is needed but not set\n");
+ net_nd_rep_packet_ip6 = net_nd_sol_packet_ip6;
+ } else {
+ net_nd_rep_packet_ip6 = net_gateway6;
+ }
+ } else {
+ net_nd_rep_packet_ip6 = net_nd_sol_packet_ip6;
+ }
+
+ ip6_send_ns(&net_nd_rep_packet_ip6);
+}
+
+int ndisc_timeout_check(void)
+{
+ ulong t;
+
+ if (ip6_is_unspecified_addr(&net_nd_sol_packet_ip6))
+ return 0;
+
+ t = get_timer(0);
+
+ /* check for NDISC timeout */
+ if ((t - net_nd_timer_start) > NDISC_TIMEOUT) {
+ net_nd_try++;
+ if (net_nd_try >= NDISC_TIMEOUT_COUNT) {
+ puts("\nNeighbour discovery retry count exceeded; "
+ "starting again\n");
+ net_nd_try = 0;
+ net_set_state(NETLOOP_FAIL);
+ } else {
+ net_nd_timer_start = t;
+ ndisc_request();
+ }
+ }
+ return 1;
+}
+
+/*
+ * ndisc_init() - Make initial steps for ND state machine.
+ * Usually move variables into initial state.
+ */
+void ndisc_init(void)
+{
+ net_nd_packet_mac = NULL;
+ net_nd_tx_packet = NULL;
+ net_nd_sol_packet_ip6 = net_null_addr_ip6;
+ net_nd_rep_packet_ip6 = net_null_addr_ip6;
+ net_nd_tx_packet_size = 0;
+ net_nd_tx_packet = &net_nd_packet_buf[0] + (PKTALIGN - 1);
+ net_nd_tx_packet -= (ulong)net_nd_tx_packet % PKTALIGN;
+}
+
+/*
+ * validate_ra() - Validate the router advertisement message.
+ *
+ * @ip6: Pointer to the router advertisement packet
+ *
+ * Check if the router advertisement message is valid. Conditions are
+ * according to RFC 4861 section 6.1.2. Validation of Router Advertisement
+ * Messages.
+ *
+ * Return: true if the message is valid and false if it is invalid.
+ */
+bool validate_ra(struct ip6_hdr *ip6)
+{
+ struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1);
+
+ /* ICMP length (derived from the IP length) should be 16 or more octets. */
+ if (ip6->payload_len < 16)
+ return false;
+
+ /* Source IP Address should be a valid link-local address. */
+ if ((ntohs(ip6->saddr.s6_addr16[0]) & IPV6_LINK_LOCAL_MASK) !=
+ IPV6_LINK_LOCAL_PREFIX)
+ return false;
+
+ /*
+ * The IP Hop Limit field should have a value of 255, i.e., the packet
+ * could not possibly have been forwarded by a router.
+ */
+ if (ip6->hop_limit != 255)
+ return false;
+
+ /* ICMP checksum has already been checked in net_ip6_handler. */
+
+ if (icmp->icmp6_code != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * process_ra() - Process the router advertisement packet.
+ *
+ * @ip6: Pointer to the router advertisement packet
+ * @len: Length of the router advertisement packet
+ *
+ * Process the received router advertisement message.
+ * Although RFC 4861 requires retaining at least two router addresses, we only
+ * keep one because of the U-Boot limitations and its goal of lightweight code.
+ *
+ * Return: 0 - RA is a default router and contains valid prefix information.
+ * Non-zero - RA options are invalid or do not indicate it is a default router
+ * or do not contain valid prefix information.
+ */
+int process_ra(struct ip6_hdr *ip6, int len)
+{
+ /* Pointer to the ICMP section of the packet */
+ struct icmp6hdr *icmp = (struct icmp6hdr *)(ip6 + 1);
+ struct ra_msg *msg = (struct ra_msg *)icmp;
+ int remaining_option_len = len - IP6_HDR_SIZE - sizeof(struct ra_msg);
+ unsigned short int option_len; /* Length of each option */
+ /* Pointer to the ICMPv6 message options */
+ unsigned char *option = NULL;
+ /* 8-bit identifier of the type of ICMPv6 option */
+ unsigned char type = 0;
+ struct icmp6_ra_prefix_info *prefix = NULL;
+
+ if (len > ETH_MAX_MTU)
+ return -EMSGSIZE;
+ /* Ignore the packet if router lifetime is 0. */
+ if (!icmp->icmp6_rt_lifetime)
+ return -EOPNOTSUPP;
+
+ /* Processing the options */
+ option = msg->opt;
+ while (remaining_option_len > 0) {
+ /* The 2nd byte of the option is its length. */
+ option_len = option[1];
+ /* All included options should have a positive length. */
+ if (option_len == 0)
+ return -EINVAL;
+
+ type = option[0];
+ /* All option types except Prefix Information are ignored. */
+ switch (type) {
+ case ND_OPT_SOURCE_LL_ADDR:
+ case ND_OPT_TARGET_LL_ADDR:
+ case ND_OPT_REDIRECT_HDR:
+ case ND_OPT_MTU:
+ break;
+ case ND_OPT_PREFIX_INFO:
+ prefix = (struct icmp6_ra_prefix_info *)option;
+ /* The link-local prefix 0xfe80::/10 is ignored. */
+ if ((ntohs(prefix->prefix.s6_addr16[0]) &
+ IPV6_LINK_LOCAL_MASK) == IPV6_LINK_LOCAL_PREFIX)
+ break;
+ if (prefix->on_link && ntohl(prefix->valid_lifetime)) {
+ net_prefix_length = prefix->prefix_len;
+ net_gateway6 = ip6->saddr;
+ return 0;
+ }
+ break;
+ default:
+ debug("Unknown IPv6 Neighbor Discovery Option 0x%x\n",
+ type);
+ }
+
+ option_len <<= 3; /* Option length is a multiple of 8. */
+ remaining_option_len -= option_len;
+ option += option_len;
+ }
+
+ return -EADDRNOTAVAIL;
+}
+
+int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len)
+{
+ struct icmp6hdr *icmp =
+ (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE);
+ struct nd_msg *ndisc = (struct nd_msg *)icmp;
+ uchar neigh_eth_addr[6];
+ int err = 0; // The error code returned calling functions.
+
+ switch (icmp->icmp6_type) {
+ case IPV6_NDISC_NEIGHBOUR_SOLICITATION:
+ debug("received neighbor solicitation for %pI6c from %pI6c\n",
+ &ndisc->target, &ip6->saddr);
+ if (ip6_is_our_addr(&ndisc->target) &&
+ ndisc_has_option(ip6, ND_OPT_SOURCE_LL_ADDR)) {
+ ndisc_extract_enetaddr(ndisc, neigh_eth_addr);
+ ip6_send_na(neigh_eth_addr, &ip6->saddr,
+ &ndisc->target);
+ }
+ break;
+
+ case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT:
+ /* are we waiting for a reply ? */
+ if (ip6_is_unspecified_addr(&net_nd_sol_packet_ip6))
+ break;
+
+ if ((memcmp(&ndisc->target, &net_nd_rep_packet_ip6,
+ sizeof(struct in6_addr)) == 0) &&
+ ndisc_has_option(ip6, ND_OPT_TARGET_LL_ADDR)) {
+ ndisc_extract_enetaddr(ndisc, neigh_eth_addr);
+
+ /* save address for later use */
+ if (!net_nd_packet_mac)
+ net_nd_packet_mac = neigh_eth_addr;
+
+ /* modify header, and transmit it */
+ memcpy(((struct ethernet_hdr *)net_nd_tx_packet)->et_dest,
+ neigh_eth_addr, 6);
+
+ net_send_packet(net_nd_tx_packet,
+ net_nd_tx_packet_size);
+
+ /* no ND request pending now */
+ net_nd_sol_packet_ip6 = net_null_addr_ip6;
+ net_nd_tx_packet_size = 0;
+ net_nd_packet_mac = NULL;
+ }
+ break;
+ case IPV6_NDISC_ROUTER_SOLICITATION:
+ break;
+ case IPV6_NDISC_ROUTER_ADVERTISEMENT:
+ debug("Received router advertisement for %pI6c from %pI6c\n",
+ &ip6->daddr, &ip6->saddr);
+ /*
+ * If gateway and prefix are set, the RA packet is ignored. The
+ * reason is that the U-Boot code is supposed to be as compact
+ * as possible and does not need to take care of multiple
+ * routers. In addition to that, U-Boot does not want to handle
+ * scenarios like a router setting its lifetime to zero to
+ * indicate it is not routing anymore. U-Boot program has a
+ * short life when the system boots up and does not need such
+ * sophistication.
+ */
+ if (!ip6_is_unspecified_addr(&net_gateway6) &&
+ net_prefix_length != 0) {
+ break;
+ }
+ if (!validate_ra(ip6)) {
+ debug("Invalid router advertisement message.\n");
+ break;
+ }
+ err = process_ra(ip6, len);
+ if (err)
+ debug("Ignored router advertisement. Error: %d\n", err);
+ else
+ printf("Set gatewayip6: %pI6c, prefix_length: %d\n",
+ &net_gateway6, net_prefix_length);
+ break;
+ default:
+ debug("Unexpected ICMPv6 type 0x%x\n", icmp->icmp6_type);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/net/net.c b/net/net.c
new file mode 100644
index 00000000000..64bcf69d83f
--- /dev/null
+++ b/net/net.c
@@ -0,0 +1,1780 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copied from Linux Monitor (LiMon) - Networking.
+ *
+ * Copyright 1994 - 2000 Neil Russell.
+ * (See License)
+ * Copyright 2000 Roland Borde
+ * Copyright 2000 Paolo Scaffardi
+ * Copyright 2000-2002 Wolfgang Denk, wd@denx.de
+ */
+
+/*
+ * General Desription:
+ *
+ * The user interface supports commands for BOOTP, RARP, and TFTP.
+ * Also, we support ARP internally. Depending on available data,
+ * these interact as follows:
+ *
+ * BOOTP:
+ *
+ * Prerequisites: - own ethernet address
+ * We want: - own IP address
+ * - TFTP server IP address
+ * - name of bootfile
+ * Next step: ARP
+ *
+ * LINKLOCAL:
+ *
+ * Prerequisites: - own ethernet address
+ * We want: - own IP address
+ * Next step: ARP
+ *
+ * RARP:
+ *
+ * Prerequisites: - own ethernet address
+ * We want: - own IP address
+ * - TFTP server IP address
+ * Next step: ARP
+ *
+ * ARP:
+ *
+ * Prerequisites: - own ethernet address
+ * - own IP address
+ * - TFTP server IP address
+ * We want: - TFTP server ethernet address
+ * Next step: TFTP
+ *
+ * DHCP:
+ *
+ * Prerequisites: - own ethernet address
+ * We want: - IP, Netmask, ServerIP, Gateway IP
+ * - bootfilename, lease time
+ * Next step: - TFTP
+ *
+ * TFTP:
+ *
+ * Prerequisites: - own ethernet address
+ * - own IP address
+ * - TFTP server IP address
+ * - TFTP server ethernet address
+ * - name of bootfile (if unknown, we use a default name
+ * derived from our own IP address)
+ * We want: - load the boot file
+ * Next step: none
+ *
+ * NFS:
+ *
+ * Prerequisites: - own ethernet address
+ * - own IP address
+ * - name of bootfile (if unknown, we use a default name
+ * derived from our own IP address)
+ * We want: - load the boot file
+ * Next step: none
+ *
+ *
+ * WOL:
+ *
+ * Prerequisites: - own ethernet address
+ * We want: - magic packet or timeout
+ * Next step: none
+ */
+
+#include <bootstage.h>
+#include <command.h>
+#include <console.h>
+#include <env.h>
+#include <env_internal.h>
+#include <errno.h>
+#include <image.h>
+#include <led.h>
+#include <log.h>
+#include <net.h>
+#include <net6.h>
+#include <ndisc.h>
+#include <net/fastboot_udp.h>
+#include <net/fastboot_tcp.h>
+#include <net/tftp.h>
+#include <net/ncsi.h>
+#if defined(CONFIG_CMD_PCAP)
+#include <net/pcap.h>
+#endif
+#include <net/udp.h>
+#if defined(CONFIG_LED_STATUS)
+#include <miiphy.h>
+#include <status_led.h>
+#endif
+#include <watchdog.h>
+#include <linux/compiler.h>
+#include <test/test.h>
+#include <net/tcp.h>
+#include <net/wget.h>
+#include "arp.h"
+#include "bootp.h"
+#include "cdp.h"
+#if defined(CONFIG_CMD_DNS)
+#include "dns.h"
+#endif
+#include "link_local.h"
+#include "nfs.h"
+#include "ping.h"
+#include "rarp.h"
+#if defined(CONFIG_CMD_WOL)
+#include "wol.h"
+#endif
+#include "dhcpv6.h"
+#include "net_rand.h"
+
+/** BOOTP EXTENTIONS **/
+
+/* Our subnet mask (0=unknown) */
+struct in_addr net_netmask;
+/* Our gateways IP address */
+struct in_addr net_gateway;
+/* Our DNS IP address */
+struct in_addr net_dns_server;
+#if defined(CONFIG_BOOTP_DNS2)
+/* Our 2nd DNS IP address */
+struct in_addr net_dns_server2;
+#endif
+/* Indicates whether the pxe path prefix / config file was specified in dhcp option */
+char *pxelinux_configfile;
+
+/** END OF BOOTP EXTENTIONS **/
+
+/* Our ethernet address */
+u8 net_ethaddr[6];
+/* Boot server enet address */
+u8 net_server_ethaddr[6];
+/* Our IP addr (0 = unknown) */
+struct in_addr net_ip;
+/* Server IP addr (0 = unknown) */
+struct in_addr net_server_ip;
+/* Current receive packet */
+uchar *net_rx_packet;
+/* Current rx packet length */
+int net_rx_packet_len;
+/* IP packet ID */
+static unsigned net_ip_id;
+/* Ethernet bcast address */
+const u8 net_bcast_ethaddr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+const u8 net_null_ethaddr[6];
+#if defined(CONFIG_API) || defined(CONFIG_EFI_LOADER)
+void (*push_packet)(void *, int len) = 0;
+#endif
+/* Network loop state */
+enum net_loop_state net_state;
+/* Tried all network devices */
+int net_restart_wrap;
+/* Network loop restarted */
+static int net_restarted;
+/* At least one device configured */
+static int net_dev_exists;
+
+/* XXX in both little & big endian machines 0xFFFF == ntohs(-1) */
+/* default is without VLAN */
+ushort net_our_vlan = 0xFFFF;
+/* ditto */
+ushort net_native_vlan = 0xFFFF;
+
+/* Boot File name */
+char net_boot_file_name[1024];
+/* Indicates whether the file name was specified on the command line */
+bool net_boot_file_name_explicit;
+/* The actual transferred size of the bootfile (in bytes) */
+u32 net_boot_file_size;
+/* Boot file size in blocks as reported by the DHCP server */
+u32 net_boot_file_expected_size_in_blocks;
+
+static uchar net_pkt_buf[(PKTBUFSRX+1) * PKTSIZE_ALIGN + PKTALIGN];
+/* Receive packets */
+uchar *net_rx_packets[PKTBUFSRX];
+/* Current UDP RX packet handler */
+static rxhand_f *udp_packet_handler;
+/* Current ARP RX packet handler */
+static rxhand_f *arp_packet_handler;
+#ifdef CONFIG_CMD_TFTPPUT
+/* Current ICMP rx handler */
+static rxhand_icmp_f *packet_icmp_handler;
+#endif
+/* Current timeout handler */
+static thand_f *time_handler;
+/* Time base value */
+static ulong time_start;
+/* Current timeout value */
+static ulong time_delta;
+/* THE transmit packet */
+uchar *net_tx_packet;
+
+static int net_check_prereq(enum proto_t protocol);
+
+static int net_try_count;
+
+int __maybe_unused net_busy_flag;
+
+/**********************************************************************/
+
+static int on_ipaddr(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ if (flags & H_PROGRAMMATIC)
+ return 0;
+
+ net_ip = string_to_ip(value);
+
+ return 0;
+}
+U_BOOT_ENV_CALLBACK(ipaddr, on_ipaddr);
+
+static int on_gatewayip(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ if (flags & H_PROGRAMMATIC)
+ return 0;
+
+ net_gateway = string_to_ip(value);
+
+ return 0;
+}
+U_BOOT_ENV_CALLBACK(gatewayip, on_gatewayip);
+
+static int on_netmask(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ if (flags & H_PROGRAMMATIC)
+ return 0;
+
+ net_netmask = string_to_ip(value);
+
+ return 0;
+}
+U_BOOT_ENV_CALLBACK(netmask, on_netmask);
+
+static int on_serverip(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ if (flags & H_PROGRAMMATIC)
+ return 0;
+
+ net_server_ip = string_to_ip(value);
+
+ return 0;
+}
+U_BOOT_ENV_CALLBACK(serverip, on_serverip);
+
+static int on_nvlan(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ if (flags & H_PROGRAMMATIC)
+ return 0;
+
+ net_native_vlan = string_to_vlan(value);
+
+ return 0;
+}
+U_BOOT_ENV_CALLBACK(nvlan, on_nvlan);
+
+static int on_vlan(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ if (flags & H_PROGRAMMATIC)
+ return 0;
+
+ net_our_vlan = string_to_vlan(value);
+
+ return 0;
+}
+U_BOOT_ENV_CALLBACK(vlan, on_vlan);
+
+#if defined(CONFIG_CMD_DNS)
+static int on_dnsip(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ if (flags & H_PROGRAMMATIC)
+ return 0;
+
+ net_dns_server = string_to_ip(value);
+
+ return 0;
+}
+U_BOOT_ENV_CALLBACK(dnsip, on_dnsip);
+#endif
+
+/*
+ * Check if autoload is enabled. If so, use either NFS or TFTP to download
+ * the boot file.
+ */
+void net_auto_load(void)
+{
+#if defined(CONFIG_CMD_NFS) && !defined(CONFIG_XPL_BUILD)
+ const char *s = env_get("autoload");
+
+ if (s != NULL && strcmp(s, "NFS") == 0) {
+ if (net_check_prereq(NFS)) {
+/* We aren't expecting to get a serverip, so just accept the assigned IP */
+ if (IS_ENABLED(CONFIG_BOOTP_SERVERIP)) {
+ net_set_state(NETLOOP_SUCCESS);
+ } else {
+ printf("Cannot autoload with NFS\n");
+ net_set_state(NETLOOP_FAIL);
+ }
+ return;
+ }
+ /*
+ * Use NFS to load the bootfile.
+ */
+ nfs_start();
+ return;
+ }
+#endif
+ if (env_get_yesno("autoload") == 0) {
+ /*
+ * Just use BOOTP/RARP to configure system;
+ * Do not use TFTP to load the bootfile.
+ */
+ net_set_state(NETLOOP_SUCCESS);
+ return;
+ }
+ if (IS_ENABLED(CONFIG_CMD_TFTPBOOT)) {
+ if (net_check_prereq(TFTPGET)) {
+ /*
+ * We aren't expecting to get a serverip, so just
+ * accept the assigned IP
+ */
+ if (IS_ENABLED(CONFIG_BOOTP_SERVERIP)) {
+ net_set_state(NETLOOP_SUCCESS);
+ } else {
+ printf("Cannot autoload with TFTPGET\n");
+ net_set_state(NETLOOP_FAIL);
+ }
+ return;
+ }
+ tftp_start(TFTPGET);
+ }
+}
+
+static int net_init_loop(void)
+{
+ static bool first_call = true;
+
+ if (eth_get_dev()) {
+ memcpy(net_ethaddr, eth_get_ethaddr(), 6);
+
+ if (IS_ENABLED(CONFIG_IPV6)) {
+ ip6_make_lladdr(&net_link_local_ip6, net_ethaddr);
+ if (!memcmp(&net_ip6, &net_null_addr_ip6,
+ sizeof(struct in6_addr)))
+ memcpy(&net_ip6, &net_link_local_ip6,
+ sizeof(struct in6_addr));
+ }
+ }
+ else
+ /*
+ * Not ideal, but there's no way to get the actual error, and I
+ * don't feel like fixing all the users of eth_get_dev to deal
+ * with errors.
+ */
+ return -ENONET;
+
+ if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY))
+ if (first_call && use_ip6) {
+ first_call = false;
+ srand_mac(); /* This is for rand used in ip6_send_rs. */
+ net_loop(RS);
+ }
+ return 0;
+}
+
+static void net_clear_handlers(void)
+{
+ net_set_udp_handler(NULL);
+ net_set_arp_handler(NULL);
+ net_set_timeout_handler(0, NULL);
+}
+
+static void net_cleanup_loop(void)
+{
+ net_clear_handlers();
+}
+
+int net_init(void)
+{
+ static int first_call = 1;
+
+ if (first_call) {
+ /*
+ * Setup packet buffers, aligned correctly.
+ */
+ int i;
+
+ net_tx_packet = &net_pkt_buf[0] + (PKTALIGN - 1);
+ net_tx_packet -= (ulong)net_tx_packet % PKTALIGN;
+ for (i = 0; i < PKTBUFSRX; i++) {
+ net_rx_packets[i] = net_tx_packet +
+ (i + 1) * PKTSIZE_ALIGN;
+ }
+ arp_init();
+ ndisc_init();
+ net_clear_handlers();
+
+ /* Only need to setup buffer pointers once. */
+ first_call = 0;
+ if (IS_ENABLED(CONFIG_PROT_TCP))
+ tcp_set_tcp_state(TCP_CLOSED);
+ }
+
+ return net_init_loop();
+}
+
+/**********************************************************************/
+/*
+ * Main network processing loop.
+ */
+
+int net_loop(enum proto_t protocol)
+{
+ int ret = -EINVAL;
+ enum net_loop_state prev_net_state = net_state;
+
+#if defined(CONFIG_CMD_PING)
+ if (protocol != PING)
+ net_ping_ip.s_addr = 0;
+#endif
+ net_restarted = 0;
+ net_dev_exists = 0;
+ net_try_count = 1;
+ debug_cond(DEBUG_INT_STATE, "--- net_loop Entry\n");
+
+#ifdef CONFIG_PHY_NCSI
+ if (phy_interface_is_ncsi() && protocol != NCSI && !ncsi_active()) {
+ printf("%s: configuring NCSI first\n", __func__);
+ if (net_loop(NCSI) < 0)
+ return ret;
+ eth_init_state_only();
+ goto restart;
+ }
+#endif
+
+ bootstage_mark_name(BOOTSTAGE_ID_ETH_START, "eth_start");
+ net_init();
+ if (eth_is_on_demand_init()) {
+ eth_halt();
+ eth_set_current();
+ ret = eth_init();
+ if (ret < 0) {
+ eth_halt();
+ return ret;
+ }
+ } else {
+ eth_init_state_only();
+ }
+
+restart:
+#ifdef CONFIG_USB_KEYBOARD
+ net_busy_flag = 0;
+#endif
+ net_set_state(NETLOOP_CONTINUE);
+
+ /*
+ * Start the ball rolling with the given start function. From
+ * here on, this code is a state machine driven by received
+ * packets and timer events.
+ */
+ debug_cond(DEBUG_INT_STATE, "--- net_loop Init\n");
+ net_init_loop();
+
+ if (!test_eth_enabled())
+ return 0;
+
+ switch (net_check_prereq(protocol)) {
+ case 1:
+ /* network not configured */
+ eth_halt();
+ net_set_state(prev_net_state);
+ return -ENODEV;
+
+ case 2:
+ /* network device not configured */
+ break;
+
+ case 0:
+ net_dev_exists = 1;
+ net_boot_file_size = 0;
+ switch (protocol) {
+#ifdef CONFIG_CMD_TFTPBOOT
+ case TFTPGET:
+#ifdef CONFIG_CMD_TFTPPUT
+ case TFTPPUT:
+#endif
+ /* always use ARP to get server ethernet address */
+ tftp_start(protocol);
+ break;
+#endif
+#ifdef CONFIG_CMD_TFTPSRV
+ case TFTPSRV:
+ tftp_start_server();
+ break;
+#endif
+#if CONFIG_IS_ENABLED(UDP_FUNCTION_FASTBOOT)
+ case FASTBOOT_UDP:
+ fastboot_udp_start_server();
+ break;
+#endif
+#if CONFIG_IS_ENABLED(TCP_FUNCTION_FASTBOOT)
+ case FASTBOOT_TCP:
+ fastboot_tcp_start_server();
+ break;
+#endif
+#if defined(CONFIG_CMD_DHCP)
+ case DHCP:
+ bootp_reset();
+ net_ip.s_addr = 0;
+ dhcp_request(); /* Basically same as BOOTP */
+ break;
+#endif
+ case DHCP6:
+ if (IS_ENABLED(CONFIG_CMD_DHCP6))
+ dhcp6_start();
+ break;
+#if defined(CONFIG_CMD_BOOTP)
+ case BOOTP:
+ bootp_reset();
+ net_ip.s_addr = 0;
+ bootp_request();
+ break;
+#endif
+#if defined(CONFIG_CMD_RARP)
+ case RARP:
+ rarp_try = 0;
+ net_ip.s_addr = 0;
+ rarp_request();
+ break;
+#endif
+#if defined(CONFIG_CMD_PING)
+ case PING:
+ ping_start();
+ break;
+#endif
+#if defined(CONFIG_CMD_PING6)
+ case PING6:
+ ping6_start();
+ break;
+#endif
+#if defined(CONFIG_CMD_NFS) && !defined(CONFIG_XPL_BUILD)
+ case NFS:
+ nfs_start();
+ break;
+#endif
+#if defined(CONFIG_CMD_WGET)
+ case WGET:
+ wget_start();
+ break;
+#endif
+#if defined(CONFIG_CMD_CDP)
+ case CDP:
+ cdp_start();
+ break;
+#endif
+#if defined(CONFIG_NETCONSOLE) && !defined(CONFIG_XPL_BUILD)
+ case NETCONS:
+ nc_start();
+ break;
+#endif
+#if defined(CONFIG_CMD_DNS)
+ case DNS:
+ dns_start();
+ break;
+#endif
+#if defined(CONFIG_CMD_LINK_LOCAL)
+ case LINKLOCAL:
+ link_local_start();
+ break;
+#endif
+#if defined(CONFIG_CMD_WOL)
+ case WOL:
+ wol_start();
+ break;
+#endif
+#if defined(CONFIG_PHY_NCSI)
+ case NCSI:
+ ncsi_probe_packages();
+ break;
+#endif
+ case RS:
+ if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY))
+ ip6_send_rs();
+ break;
+ default:
+ break;
+ }
+
+ if (IS_ENABLED(CONFIG_PROT_UDP) && protocol == UDP)
+ udp_start();
+
+ break;
+ }
+
+#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII)
+#if defined(CONFIG_SYS_FAULT_ECHO_LINK_DOWN) && \
+ defined(CONFIG_LED_STATUS) && \
+ defined(CONFIG_LED_STATUS_RED)
+ /*
+ * Echo the inverted link state to the fault LED.
+ */
+ if (miiphy_link(eth_get_dev()->name, CONFIG_SYS_FAULT_MII_ADDR))
+ status_led_set(CONFIG_LED_STATUS_RED, CONFIG_LED_STATUS_OFF);
+ else
+ status_led_set(CONFIG_LED_STATUS_RED, CONFIG_LED_STATUS_ON);
+#endif /* CONFIG_SYS_FAULT_ECHO_LINK_DOWN, ... */
+#endif /* CONFIG_MII, ... */
+#ifdef CONFIG_USB_KEYBOARD
+ net_busy_flag = 1;
+#endif
+
+ /*
+ * Main packet reception loop. Loop receiving packets until
+ * someone sets `net_state' to a state that terminates.
+ */
+ for (;;) {
+ schedule();
+ if (arp_timeout_check() > 0)
+ time_start = get_timer(0);
+
+ if (IS_ENABLED(CONFIG_IPV6)) {
+ if (use_ip6 && (ndisc_timeout_check() > 0))
+ time_start = get_timer(0);
+ }
+
+ /*
+ * Check the ethernet for a new packet. The ethernet
+ * receive routine will process it.
+ * Most drivers return the most recent packet size, but not
+ * errors that may have happened.
+ */
+ eth_rx();
+
+ /*
+ * Abort if ctrl-c was pressed.
+ */
+ if (ctrlc()) {
+ /* cancel any ARP that may not have completed */
+ net_arp_wait_packet_ip.s_addr = 0;
+
+ net_cleanup_loop();
+ eth_halt();
+ /* Invalidate the last protocol */
+ eth_set_last_protocol(BOOTP);
+
+ /* Turn off activity LED if triggered */
+ led_activity_off();
+
+ puts("\nAbort\n");
+ /* include a debug print as well incase the debug
+ messages are directed to stderr */
+ debug_cond(DEBUG_INT_STATE, "--- net_loop Abort!\n");
+ ret = -EINTR;
+ goto done;
+ }
+
+ /*
+ * Check for a timeout, and run the timeout handler
+ * if we have one.
+ */
+ if (time_handler &&
+ ((get_timer(0) - time_start) > time_delta)) {
+ thand_f *x;
+
+#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII)
+#if defined(CONFIG_SYS_FAULT_ECHO_LINK_DOWN) && \
+ defined(CONFIG_LED_STATUS) && \
+ defined(CONFIG_LED_STATUS_RED)
+ /*
+ * Echo the inverted link state to the fault LED.
+ */
+ if (miiphy_link(eth_get_dev()->name,
+ CONFIG_SYS_FAULT_MII_ADDR))
+ status_led_set(CONFIG_LED_STATUS_RED,
+ CONFIG_LED_STATUS_OFF);
+ else
+ status_led_set(CONFIG_LED_STATUS_RED,
+ CONFIG_LED_STATUS_ON);
+#endif /* CONFIG_SYS_FAULT_ECHO_LINK_DOWN, ... */
+#endif /* CONFIG_MII, ... */
+ debug_cond(DEBUG_INT_STATE, "--- net_loop timeout\n");
+ x = time_handler;
+ time_handler = (thand_f *)0;
+ (*x)();
+ } else if (IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY))
+ if (time_handler && protocol == RS)
+ if (!ip6_is_unspecified_addr(&net_gateway6) &&
+ net_prefix_length != 0) {
+ net_set_state(NETLOOP_SUCCESS);
+ net_set_timeout_handler(0, NULL);
+ }
+
+ if (net_state == NETLOOP_FAIL)
+ ret = net_start_again();
+
+ switch (net_state) {
+ case NETLOOP_RESTART:
+ net_restarted = 1;
+ goto restart;
+
+ case NETLOOP_SUCCESS:
+ net_cleanup_loop();
+ if (net_boot_file_size > 0) {
+ printf("Bytes transferred = %u (%x hex)\n",
+ net_boot_file_size, net_boot_file_size);
+ env_set_hex("filesize", net_boot_file_size);
+ env_set_hex("fileaddr", image_load_addr);
+ }
+ if (protocol != NETCONS && protocol != NCSI)
+ eth_halt();
+ else
+ eth_halt_state_only();
+
+ eth_set_last_protocol(protocol);
+
+ ret = net_boot_file_size;
+ debug_cond(DEBUG_INT_STATE, "--- net_loop Success!\n");
+ goto done;
+
+ case NETLOOP_FAIL:
+ net_cleanup_loop();
+ /* Invalidate the last protocol */
+ eth_set_last_protocol(BOOTP);
+ debug_cond(DEBUG_INT_STATE, "--- net_loop Fail!\n");
+ ret = -ENONET;
+ goto done;
+
+ case NETLOOP_CONTINUE:
+ continue;
+ }
+ }
+
+done:
+#ifdef CONFIG_USB_KEYBOARD
+ net_busy_flag = 0;
+#endif
+#ifdef CONFIG_CMD_TFTPPUT
+ /* Clear out the handlers */
+ net_set_udp_handler(NULL);
+ net_set_icmp_handler(NULL);
+#endif
+ net_set_state(prev_net_state);
+
+#if defined(CONFIG_CMD_PCAP)
+ if (pcap_active())
+ pcap_print_status();
+#endif
+ return ret;
+}
+
+/**********************************************************************/
+
+static void start_again_timeout_handler(void)
+{
+ net_set_state(NETLOOP_RESTART);
+}
+
+int net_start_again(void)
+{
+ char *nretry;
+ int retry_forever = 0;
+ unsigned long retrycnt = 0;
+ int ret;
+
+ nretry = env_get("netretry");
+ if (nretry) {
+ if (!strcmp(nretry, "yes"))
+ retry_forever = 1;
+ else if (!strcmp(nretry, "no"))
+ retrycnt = 0;
+ else if (!strcmp(nretry, "once"))
+ retrycnt = 1;
+ else
+ retrycnt = simple_strtoul(nretry, NULL, 0);
+ } else {
+ retrycnt = 0;
+ retry_forever = 0;
+ }
+
+ if ((!retry_forever) && (net_try_count > retrycnt)) {
+ eth_halt();
+ net_set_state(NETLOOP_FAIL);
+ /*
+ * We don't provide a way for the protocol to return an error,
+ * but this is almost always the reason.
+ */
+ return -ETIMEDOUT;
+ }
+
+ net_try_count++;
+
+ eth_halt();
+#if !defined(CONFIG_NET_DO_NOT_TRY_ANOTHER)
+ eth_try_another(!net_restarted);
+#endif
+ ret = eth_init();
+ if (net_restart_wrap) {
+ net_restart_wrap = 0;
+ if (net_dev_exists) {
+ net_set_timeout_handler(10000UL,
+ start_again_timeout_handler);
+ net_set_udp_handler(NULL);
+ } else {
+ net_set_state(NETLOOP_FAIL);
+ }
+ } else {
+ net_set_state(NETLOOP_RESTART);
+ }
+ return ret;
+}
+
+/**********************************************************************/
+/*
+ * Miscelaneous bits.
+ */
+
+static void dummy_handler(uchar *pkt, unsigned dport,
+ struct in_addr sip, unsigned sport,
+ unsigned len)
+{
+}
+
+rxhand_f *net_get_udp_handler(void)
+{
+ return udp_packet_handler;
+}
+
+void net_set_udp_handler(rxhand_f *f)
+{
+ debug_cond(DEBUG_INT_STATE, "--- net_loop UDP handler set (%p)\n", f);
+ if (f == NULL)
+ udp_packet_handler = dummy_handler;
+ else
+ udp_packet_handler = f;
+}
+
+rxhand_f *net_get_arp_handler(void)
+{
+ return arp_packet_handler;
+}
+
+void net_set_arp_handler(rxhand_f *f)
+{
+ debug_cond(DEBUG_INT_STATE, "--- net_loop ARP handler set (%p)\n", f);
+ if (f == NULL)
+ arp_packet_handler = dummy_handler;
+ else
+ arp_packet_handler = f;
+}
+
+#ifdef CONFIG_CMD_TFTPPUT
+void net_set_icmp_handler(rxhand_icmp_f *f)
+{
+ packet_icmp_handler = f;
+}
+#endif
+
+void net_set_timeout_handler(ulong iv, thand_f *f)
+{
+ if (iv == 0) {
+ debug_cond(DEBUG_INT_STATE,
+ "--- net_loop timeout handler cancelled\n");
+ time_handler = (thand_f *)0;
+ } else {
+ debug_cond(DEBUG_INT_STATE,
+ "--- net_loop timeout handler set (%p)\n", f);
+ time_handler = f;
+ time_start = get_timer(0);
+ time_delta = iv * CONFIG_SYS_HZ / 1000;
+ }
+}
+
+uchar *net_get_async_tx_pkt_buf(void)
+{
+ if (arp_is_waiting())
+ return arp_tx_packet; /* If we are waiting, we already sent */
+ else
+ return net_tx_packet;
+}
+
+int net_send_udp_packet(uchar *ether, struct in_addr dest, int dport, int sport,
+ int payload_len)
+{
+ return net_send_ip_packet(ether, dest, dport, sport, payload_len,
+ IPPROTO_UDP, 0, 0, 0);
+}
+
+#if defined(CONFIG_PROT_TCP)
+int net_send_tcp_packet(int payload_len, int dport, int sport, u8 action,
+ u32 tcp_seq_num, u32 tcp_ack_num)
+{
+ return net_send_ip_packet(net_server_ethaddr, net_server_ip, dport,
+ sport, payload_len, IPPROTO_TCP, action,
+ tcp_seq_num, tcp_ack_num);
+}
+#endif
+
+int net_send_ip_packet(uchar *ether, struct in_addr dest, int dport, int sport,
+ int payload_len, int proto, u8 action, u32 tcp_seq_num,
+ u32 tcp_ack_num)
+{
+ uchar *pkt;
+ int eth_hdr_size;
+ int pkt_hdr_size;
+
+ /* make sure the net_tx_packet is initialized (net_init() was called) */
+ assert(net_tx_packet != NULL);
+ if (net_tx_packet == NULL)
+ return -1;
+
+ /* convert to new style broadcast */
+ if (dest.s_addr == 0)
+ dest.s_addr = 0xFFFFFFFF;
+
+ /* if broadcast, make the ether address a broadcast and don't do ARP */
+ if (dest.s_addr == 0xFFFFFFFF)
+ ether = (uchar *)net_bcast_ethaddr;
+
+ pkt = (uchar *)net_tx_packet;
+
+ eth_hdr_size = net_set_ether(pkt, ether, PROT_IP);
+
+ switch (proto) {
+ case IPPROTO_UDP:
+ net_set_udp_header(pkt + eth_hdr_size, dest, dport, sport,
+ payload_len);
+ pkt_hdr_size = eth_hdr_size + IP_UDP_HDR_SIZE;
+ break;
+#if defined(CONFIG_PROT_TCP)
+ case IPPROTO_TCP:
+ pkt_hdr_size = eth_hdr_size
+ + tcp_set_tcp_header(pkt + eth_hdr_size, dport, sport,
+ payload_len, action, tcp_seq_num,
+ tcp_ack_num);
+ break;
+#endif
+ default:
+ return -EINVAL;
+ }
+
+ /* if MAC address was not discovered yet, do an ARP request */
+ if (memcmp(ether, net_null_ethaddr, 6) == 0) {
+ debug_cond(DEBUG_DEV_PKT, "sending ARP for %pI4\n", &dest);
+
+ /* save the ip and eth addr for the packet to send after arp */
+ net_arp_wait_packet_ip = dest;
+ arp_wait_packet_ethaddr = ether;
+
+ /* size of the waiting packet */
+ arp_wait_tx_packet_size = pkt_hdr_size + payload_len;
+
+ /* and do the ARP request */
+ arp_wait_try = 1;
+ arp_wait_timer_start = get_timer(0);
+ arp_request();
+ return 1; /* waiting */
+ } else {
+ debug_cond(DEBUG_DEV_PKT, "sending UDP to %pI4/%pM\n",
+ &dest, ether);
+ net_send_packet(net_tx_packet, pkt_hdr_size + payload_len);
+ return 0; /* transmitted */
+ }
+}
+
+#ifdef CONFIG_IP_DEFRAG
+/*
+ * This function collects fragments in a single packet, according
+ * to the algorithm in RFC815. It returns NULL or the pointer to
+ * a complete packet, in static storage
+ */
+#define IP_PKTSIZE (CONFIG_NET_MAXDEFRAG)
+
+#define IP_MAXUDP (IP_PKTSIZE - IP_HDR_SIZE)
+
+/*
+ * this is the packet being assembled, either data or frag control.
+ * Fragments go by 8 bytes, so this union must be 8 bytes long
+ */
+struct hole {
+ /* first_byte is address of this structure */
+ u16 last_byte; /* last byte in this hole + 1 (begin of next hole) */
+ u16 next_hole; /* index of next (in 8-b blocks), 0 == none */
+ u16 prev_hole; /* index of prev, 0 == none */
+ u16 unused;
+};
+
+static struct ip_udp_hdr *__net_defragment(struct ip_udp_hdr *ip, int *lenp)
+{
+ static uchar pkt_buff[IP_PKTSIZE] __aligned(PKTALIGN);
+ static u16 first_hole, total_len;
+ struct hole *payload, *thisfrag, *h, *newh;
+ struct ip_udp_hdr *localip = (struct ip_udp_hdr *)pkt_buff;
+ uchar *indata = (uchar *)ip;
+ int offset8, start, len, done = 0;
+ u16 ip_off = ntohs(ip->ip_off);
+
+ /*
+ * Calling code already rejected <, but we don't have to deal
+ * with an IP fragment with no payload.
+ */
+ if (ntohs(ip->ip_len) <= IP_HDR_SIZE)
+ return NULL;
+
+ /* payload starts after IP header, this fragment is in there */
+ payload = (struct hole *)(pkt_buff + IP_HDR_SIZE);
+ offset8 = (ip_off & IP_OFFS);
+ thisfrag = payload + offset8;
+ start = offset8 * 8;
+ len = ntohs(ip->ip_len) - IP_HDR_SIZE;
+
+ /* All but last fragment must have a multiple-of-8 payload. */
+ if ((len & 7) && (ip_off & IP_FLAGS_MFRAG))
+ return NULL;
+
+ if (start + len > IP_MAXUDP) /* fragment extends too far */
+ return NULL;
+
+ if (!total_len || localip->ip_id != ip->ip_id) {
+ /* new (or different) packet, reset structs */
+ total_len = 0xffff;
+ payload[0].last_byte = ~0;
+ payload[0].next_hole = 0;
+ payload[0].prev_hole = 0;
+ first_hole = 0;
+ /* any IP header will work, copy the first we received */
+ memcpy(localip, ip, IP_HDR_SIZE);
+ }
+
+ /*
+ * What follows is the reassembly algorithm. We use the payload
+ * array as a linked list of hole descriptors, as each hole starts
+ * at a multiple of 8 bytes. However, last byte can be whatever value,
+ * so it is represented as byte count, not as 8-byte blocks.
+ */
+
+ h = payload + first_hole;
+ while (h->last_byte < start) {
+ if (!h->next_hole) {
+ /* no hole that far away */
+ return NULL;
+ }
+ h = payload + h->next_hole;
+ }
+
+ /* last fragment may be 1..7 bytes, the "+7" forces acceptance */
+ if (offset8 + ((len + 7) / 8) <= h - payload) {
+ /* no overlap with holes (dup fragment?) */
+ return NULL;
+ }
+
+ if (!(ip_off & IP_FLAGS_MFRAG)) {
+ /* no more fragmentss: truncate this (last) hole */
+ total_len = start + len;
+ h->last_byte = start + len;
+ }
+
+ /*
+ * There is some overlap: fix the hole list. This code deals
+ * with a fragment that overlaps with two different holes
+ * (thus being a superset of a previously-received fragment)
+ * by only using the part of the fragment that fits in the
+ * first hole.
+ */
+ if (h->last_byte < start + len)
+ len = h->last_byte - start;
+
+ if ((h >= thisfrag) && (h->last_byte <= start + len)) {
+ /* complete overlap with hole: remove hole */
+ if (!h->prev_hole && !h->next_hole) {
+ /* last remaining hole */
+ done = 1;
+ } else if (!h->prev_hole) {
+ /* first hole */
+ first_hole = h->next_hole;
+ payload[h->next_hole].prev_hole = 0;
+ } else if (!h->next_hole) {
+ /* last hole */
+ payload[h->prev_hole].next_hole = 0;
+ } else {
+ /* in the middle of the list */
+ payload[h->next_hole].prev_hole = h->prev_hole;
+ payload[h->prev_hole].next_hole = h->next_hole;
+ }
+
+ } else if (h->last_byte <= start + len) {
+ /* overlaps with final part of the hole: shorten this hole */
+ h->last_byte = start;
+
+ } else if (h >= thisfrag) {
+ /* overlaps with initial part of the hole: move this hole */
+ newh = thisfrag + (len / 8);
+ *newh = *h;
+ h = newh;
+ if (h->next_hole)
+ payload[h->next_hole].prev_hole = (h - payload);
+ if (h->prev_hole)
+ payload[h->prev_hole].next_hole = (h - payload);
+ else
+ first_hole = (h - payload);
+
+ } else {
+ /* fragment sits in the middle: split the hole */
+ newh = thisfrag + (len / 8);
+ *newh = *h;
+ h->last_byte = start;
+ h->next_hole = (newh - payload);
+ newh->prev_hole = (h - payload);
+ if (newh->next_hole)
+ payload[newh->next_hole].prev_hole = (newh - payload);
+ }
+
+ /* finally copy this fragment and possibly return whole packet */
+ memcpy((uchar *)thisfrag, indata + IP_HDR_SIZE, len);
+ if (!done)
+ return NULL;
+
+ *lenp = total_len + IP_HDR_SIZE;
+ localip->ip_len = htons(*lenp);
+ return localip;
+}
+
+static inline struct ip_udp_hdr *net_defragment(struct ip_udp_hdr *ip,
+ int *lenp)
+{
+ u16 ip_off = ntohs(ip->ip_off);
+ if (!(ip_off & (IP_OFFS | IP_FLAGS_MFRAG)))
+ return ip; /* not a fragment */
+ return __net_defragment(ip, lenp);
+}
+
+#else /* !CONFIG_IP_DEFRAG */
+
+static inline struct ip_udp_hdr *net_defragment(struct ip_udp_hdr *ip,
+ int *lenp)
+{
+ u16 ip_off = ntohs(ip->ip_off);
+ if (!(ip_off & (IP_OFFS | IP_FLAGS_MFRAG)))
+ return ip; /* not a fragment */
+ return NULL;
+}
+#endif
+
+/**
+ * Receive an ICMP packet. We deal with REDIRECT and PING here, and silently
+ * drop others.
+ *
+ * @parma ip IP packet containing the ICMP
+ */
+static void receive_icmp(struct ip_udp_hdr *ip, int len,
+ struct in_addr src_ip, struct ethernet_hdr *et)
+{
+ struct icmp_hdr *icmph = (struct icmp_hdr *)&ip->udp_src;
+
+ switch (icmph->type) {
+ case ICMP_REDIRECT:
+ if (icmph->code != ICMP_REDIR_HOST)
+ return;
+ printf(" ICMP Host Redirect to %pI4 ",
+ &icmph->un.gateway);
+ break;
+ default:
+#if defined(CONFIG_CMD_PING)
+ ping_receive(et, ip, len);
+#endif
+#ifdef CONFIG_CMD_TFTPPUT
+ if (packet_icmp_handler)
+ packet_icmp_handler(icmph->type, icmph->code,
+ ntohs(ip->udp_dst), src_ip,
+ ntohs(ip->udp_src), icmph->un.data,
+ ntohs(ip->udp_len));
+#endif
+ break;
+ }
+}
+
+void net_process_received_packet(uchar *in_packet, int len)
+{
+ struct ethernet_hdr *et;
+ struct ip_udp_hdr *ip;
+ struct in_addr dst_ip;
+ struct in_addr src_ip;
+ int eth_proto;
+#if defined(CONFIG_CMD_CDP)
+ int iscdp;
+#endif
+ ushort cti = 0, vlanid = VLAN_NONE, myvlanid, mynvlanid;
+
+ debug_cond(DEBUG_NET_PKT, "packet received\n");
+ if (DEBUG_NET_PKT_TRACE)
+ print_hex_dump_bytes("rx: ", DUMP_PREFIX_OFFSET, in_packet,
+ len);
+
+#if defined(CONFIG_CMD_PCAP)
+ pcap_post(in_packet, len, false);
+#endif
+ net_rx_packet = in_packet;
+ net_rx_packet_len = len;
+ et = (struct ethernet_hdr *)in_packet;
+
+ /* too small packet? */
+ if (len < ETHER_HDR_SIZE)
+ return;
+
+#if defined(CONFIG_API) || defined(CONFIG_EFI_LOADER)
+ if (push_packet) {
+ (*push_packet)(in_packet, len);
+ return;
+ }
+#endif
+
+#if defined(CONFIG_CMD_CDP)
+ /* keep track if packet is CDP */
+ iscdp = is_cdp_packet(et->et_dest);
+#endif
+
+ myvlanid = ntohs(net_our_vlan);
+ if (myvlanid == (ushort)-1)
+ myvlanid = VLAN_NONE;
+ mynvlanid = ntohs(net_native_vlan);
+ if (mynvlanid == (ushort)-1)
+ mynvlanid = VLAN_NONE;
+
+ eth_proto = ntohs(et->et_protlen);
+
+ if (eth_proto < 1514) {
+ struct e802_hdr *et802 = (struct e802_hdr *)et;
+ /*
+ * Got a 802.2 packet. Check the other protocol field.
+ * XXX VLAN over 802.2+SNAP not implemented!
+ */
+ eth_proto = ntohs(et802->et_prot);
+
+ ip = (struct ip_udp_hdr *)(in_packet + E802_HDR_SIZE);
+ len -= E802_HDR_SIZE;
+
+ } else if (eth_proto != PROT_VLAN) { /* normal packet */
+ ip = (struct ip_udp_hdr *)(in_packet + ETHER_HDR_SIZE);
+ len -= ETHER_HDR_SIZE;
+
+ } else { /* VLAN packet */
+ struct vlan_ethernet_hdr *vet =
+ (struct vlan_ethernet_hdr *)et;
+
+ debug_cond(DEBUG_NET_PKT, "VLAN packet received\n");
+
+ /* too small packet? */
+ if (len < VLAN_ETHER_HDR_SIZE)
+ return;
+
+ /* if no VLAN active */
+ if ((ntohs(net_our_vlan) & VLAN_IDMASK) == VLAN_NONE
+#if defined(CONFIG_CMD_CDP)
+ && iscdp == 0
+#endif
+ )
+ return;
+
+ cti = ntohs(vet->vet_tag);
+ vlanid = cti & VLAN_IDMASK;
+ eth_proto = ntohs(vet->vet_type);
+
+ ip = (struct ip_udp_hdr *)(in_packet + VLAN_ETHER_HDR_SIZE);
+ len -= VLAN_ETHER_HDR_SIZE;
+ }
+
+ debug_cond(DEBUG_NET_PKT, "Receive from protocol 0x%x\n", eth_proto);
+
+#if defined(CONFIG_CMD_CDP)
+ if (iscdp) {
+ cdp_receive((uchar *)ip, len);
+ return;
+ }
+#endif
+
+ if ((myvlanid & VLAN_IDMASK) != VLAN_NONE) {
+ if (vlanid == VLAN_NONE)
+ vlanid = (mynvlanid & VLAN_IDMASK);
+ /* not matched? */
+ if (vlanid != (myvlanid & VLAN_IDMASK))
+ return;
+ }
+
+ switch (eth_proto) {
+ case PROT_ARP:
+ arp_receive(et, ip, len);
+ break;
+
+#ifdef CONFIG_CMD_RARP
+ case PROT_RARP:
+ rarp_receive(ip, len);
+ break;
+#endif
+#if IS_ENABLED(CONFIG_IPV6)
+ case PROT_IP6:
+ net_ip6_handler(et, (struct ip6_hdr *)ip, len);
+ break;
+#endif
+ case PROT_IP:
+ debug_cond(DEBUG_NET_PKT, "Got IP\n");
+ /* Before we start poking the header, make sure it is there */
+ if (len < IP_HDR_SIZE) {
+ debug("len bad %d < %lu\n", len,
+ (ulong)IP_HDR_SIZE);
+ return;
+ }
+ /* Check the packet length */
+ if (len < ntohs(ip->ip_len)) {
+ debug("len bad %d < %d\n", len, ntohs(ip->ip_len));
+ return;
+ }
+ len = ntohs(ip->ip_len);
+ if (len < IP_HDR_SIZE) {
+ debug("bad ip->ip_len %d < %d\n", len, (int)IP_HDR_SIZE);
+ return;
+ }
+ debug_cond(DEBUG_NET_PKT, "len=%d, v=%02x\n",
+ len, ip->ip_hl_v & 0xff);
+
+ /* Can't deal with anything except IPv4 */
+ if ((ip->ip_hl_v & 0xf0) != 0x40)
+ return;
+ /* Can't deal with IP options (headers != 20 bytes) */
+ if ((ip->ip_hl_v & 0x0f) != 0x05)
+ return;
+ /* Check the Checksum of the header */
+ if (!ip_checksum_ok((uchar *)ip, IP_HDR_SIZE)) {
+ debug("checksum bad\n");
+ return;
+ }
+ /* If it is not for us, ignore it */
+ dst_ip = net_read_ip(&ip->ip_dst);
+ if (net_ip.s_addr && dst_ip.s_addr != net_ip.s_addr &&
+ dst_ip.s_addr != 0xFFFFFFFF) {
+ return;
+ }
+ /* Read source IP address for later use */
+ src_ip = net_read_ip(&ip->ip_src);
+ /*
+ * The function returns the unchanged packet if it's not
+ * a fragment, and either the complete packet or NULL if
+ * it is a fragment (if !CONFIG_IP_DEFRAG, it returns NULL)
+ */
+ ip = net_defragment(ip, &len);
+ if (!ip)
+ return;
+ /*
+ * watch for ICMP host redirects
+ *
+ * There is no real handler code (yet). We just watch
+ * for ICMP host redirect messages. In case anybody
+ * sees these messages: please contact me
+ * (wd@denx.de), or - even better - send me the
+ * necessary fixes :-)
+ *
+ * Note: in all cases where I have seen this so far
+ * it was a problem with the router configuration,
+ * for instance when a router was configured in the
+ * BOOTP reply, but the TFTP server was on the same
+ * subnet. So this is probably a warning that your
+ * configuration might be wrong. But I'm not really
+ * sure if there aren't any other situations.
+ *
+ * Simon Glass <sjg@chromium.org>: We get an ICMP when
+ * we send a tftp packet to a dead connection, or when
+ * there is no server at the other end.
+ */
+ if (ip->ip_p == IPPROTO_ICMP) {
+ receive_icmp(ip, len, src_ip, et);
+ return;
+#if defined(CONFIG_PROT_TCP)
+ } else if (ip->ip_p == IPPROTO_TCP) {
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP PH (to=%pI4, from=%pI4, len=%d)\n",
+ &dst_ip, &src_ip, len);
+
+ rxhand_tcp_f((union tcp_build_pkt *)ip, len);
+ return;
+#endif
+ } else if (ip->ip_p != IPPROTO_UDP) { /* Only UDP packets */
+ return;
+ }
+
+ if (ntohs(ip->udp_len) < UDP_HDR_SIZE || ntohs(ip->udp_len) > len - IP_HDR_SIZE)
+ return;
+
+ debug_cond(DEBUG_DEV_PKT,
+ "received UDP (to=%pI4, from=%pI4, len=%d)\n",
+ &dst_ip, &src_ip, len);
+
+ if (IS_ENABLED(CONFIG_UDP_CHECKSUM) && ip->udp_xsum != 0) {
+ ulong xsum;
+ u8 *sumptr;
+ ushort sumlen;
+
+ xsum = ip->ip_p;
+ xsum += (ntohs(ip->udp_len));
+ xsum += (ntohl(ip->ip_src.s_addr) >> 16) & 0x0000ffff;
+ xsum += (ntohl(ip->ip_src.s_addr) >> 0) & 0x0000ffff;
+ xsum += (ntohl(ip->ip_dst.s_addr) >> 16) & 0x0000ffff;
+ xsum += (ntohl(ip->ip_dst.s_addr) >> 0) & 0x0000ffff;
+
+ sumlen = ntohs(ip->udp_len);
+ sumptr = (u8 *)&ip->udp_src;
+
+ while (sumlen > 1) {
+ /* inlined ntohs() to avoid alignment errors */
+ xsum += (sumptr[0] << 8) + sumptr[1];
+ sumptr += 2;
+ sumlen -= 2;
+ }
+ if (sumlen > 0)
+ xsum += (sumptr[0] << 8) + sumptr[0];
+ while ((xsum >> 16) != 0) {
+ xsum = (xsum & 0x0000ffff) +
+ ((xsum >> 16) & 0x0000ffff);
+ }
+ if ((xsum != 0x00000000) && (xsum != 0x0000ffff)) {
+ printf(" UDP wrong checksum %08lx %08x\n",
+ xsum, ntohs(ip->udp_xsum));
+ return;
+ }
+ }
+
+#if defined(CONFIG_NETCONSOLE) && !defined(CONFIG_XPL_BUILD)
+ nc_input_packet((uchar *)ip + IP_UDP_HDR_SIZE,
+ src_ip,
+ ntohs(ip->udp_dst),
+ ntohs(ip->udp_src),
+ ntohs(ip->udp_len) - UDP_HDR_SIZE);
+#endif
+ /*
+ * IP header OK. Pass the packet to the current handler.
+ */
+ (*udp_packet_handler)((uchar *)ip + IP_UDP_HDR_SIZE,
+ ntohs(ip->udp_dst),
+ src_ip,
+ ntohs(ip->udp_src),
+ ntohs(ip->udp_len) - UDP_HDR_SIZE);
+ break;
+#ifdef CONFIG_CMD_WOL
+ case PROT_WOL:
+ wol_receive(ip, len);
+ break;
+#endif
+#ifdef CONFIG_PHY_NCSI
+ case PROT_NCSI:
+ ncsi_receive(et, ip, len);
+ break;
+#endif
+ }
+}
+
+/**********************************************************************/
+
+static int net_check_prereq(enum proto_t protocol)
+{
+ switch (protocol) {
+ /* Fall through */
+#if defined(CONFIG_CMD_PING)
+ case PING:
+ if (net_ping_ip.s_addr == 0) {
+ puts("*** ERROR: ping address not given\n");
+ return 1;
+ }
+ goto common;
+#endif
+#if defined(CONFIG_CMD_PING6)
+ case PING6:
+ if (ip6_is_unspecified_addr(&net_ping_ip6)) {
+ puts("*** ERROR: ping address not given\n");
+ return 1;
+ }
+ goto common;
+#endif
+#if defined(CONFIG_CMD_DNS)
+ case DNS:
+ if (net_dns_server.s_addr == 0) {
+ puts("*** ERROR: DNS server address not given\n");
+ return 1;
+ }
+ goto common;
+#endif
+#if defined(CONFIG_PROT_UDP)
+ case UDP:
+ if (udp_prereq())
+ return 1;
+ goto common;
+#endif
+
+#if defined(CONFIG_CMD_NFS)
+ case NFS:
+#endif
+ /* Fall through */
+ case TFTPGET:
+ case TFTPPUT:
+ if (IS_ENABLED(CONFIG_IPV6) && use_ip6) {
+ if (!memcmp(&net_server_ip6, &net_null_addr_ip6,
+ sizeof(struct in6_addr)) &&
+ !strchr(net_boot_file_name, '[')) {
+ puts("*** ERROR: `serverip6' not set\n");
+ return 1;
+ }
+ } else if (net_server_ip.s_addr == 0 && !is_serverip_in_cmd()) {
+ puts("*** ERROR: `serverip' not set\n");
+ return 1;
+ }
+#if defined(CONFIG_CMD_PING) || \
+ defined(CONFIG_CMD_DNS) || defined(CONFIG_PROT_UDP)
+common:
+#endif
+ /* Fall through */
+
+ case NETCONS:
+ case FASTBOOT_UDP:
+ case FASTBOOT_TCP:
+ case TFTPSRV:
+ if (IS_ENABLED(CONFIG_IPV6) && use_ip6) {
+ if (!memcmp(&net_link_local_ip6, &net_null_addr_ip6,
+ sizeof(struct in6_addr))) {
+ puts("*** ERROR: `ip6addr` not set\n");
+ return 1;
+ }
+ } else if (net_ip.s_addr == 0) {
+ puts("*** ERROR: `ipaddr' not set\n");
+ return 1;
+ }
+ /* Fall through */
+
+#ifdef CONFIG_CMD_RARP
+ case RARP:
+#endif
+#ifdef CONFIG_PHY_NCSI
+ case NCSI:
+#endif
+ case BOOTP:
+ case CDP:
+ case DHCP:
+ case LINKLOCAL:
+ if (memcmp(net_ethaddr, "\0\0\0\0\0\0", 6) == 0) {
+ int num = eth_get_dev_index();
+
+ switch (num) {
+ case -1:
+ puts("*** ERROR: No ethernet found.\n");
+ return 1;
+ case 0:
+ puts("*** ERROR: `ethaddr' not set\n");
+ break;
+ default:
+ printf("*** ERROR: `eth%daddr' not set\n",
+ num);
+ break;
+ }
+
+ net_start_again();
+ return 2;
+ }
+ /* Fall through */
+ default:
+ return 0;
+ }
+ return 0; /* OK */
+}
+/**********************************************************************/
+
+int
+net_eth_hdr_size(void)
+{
+ ushort myvlanid;
+
+ myvlanid = ntohs(net_our_vlan);
+ if (myvlanid == (ushort)-1)
+ myvlanid = VLAN_NONE;
+
+ return ((myvlanid & VLAN_IDMASK) == VLAN_NONE) ? ETHER_HDR_SIZE :
+ VLAN_ETHER_HDR_SIZE;
+}
+
+int net_set_ether(uchar *xet, const uchar *dest_ethaddr, uint prot)
+{
+ struct ethernet_hdr *et = (struct ethernet_hdr *)xet;
+ ushort myvlanid;
+
+ myvlanid = ntohs(net_our_vlan);
+ if (myvlanid == (ushort)-1)
+ myvlanid = VLAN_NONE;
+
+ memcpy(et->et_dest, dest_ethaddr, 6);
+ memcpy(et->et_src, net_ethaddr, 6);
+ if ((myvlanid & VLAN_IDMASK) == VLAN_NONE) {
+ et->et_protlen = htons(prot);
+ return ETHER_HDR_SIZE;
+ } else {
+ struct vlan_ethernet_hdr *vet =
+ (struct vlan_ethernet_hdr *)xet;
+
+ vet->vet_vlan_type = htons(PROT_VLAN);
+ vet->vet_tag = htons((0 << 5) | (myvlanid & VLAN_IDMASK));
+ vet->vet_type = htons(prot);
+ return VLAN_ETHER_HDR_SIZE;
+ }
+}
+
+int net_update_ether(struct ethernet_hdr *et, uchar *addr, uint prot)
+{
+ ushort protlen;
+
+ memcpy(et->et_dest, addr, 6);
+ memcpy(et->et_src, net_ethaddr, 6);
+ protlen = ntohs(et->et_protlen);
+ if (protlen == PROT_VLAN) {
+ struct vlan_ethernet_hdr *vet =
+ (struct vlan_ethernet_hdr *)et;
+ vet->vet_type = htons(prot);
+ return VLAN_ETHER_HDR_SIZE;
+ } else if (protlen > 1514) {
+ et->et_protlen = htons(prot);
+ return ETHER_HDR_SIZE;
+ } else {
+ /* 802.2 + SNAP */
+ struct e802_hdr *et802 = (struct e802_hdr *)et;
+ et802->et_prot = htons(prot);
+ return E802_HDR_SIZE;
+ }
+}
+
+void net_set_ip_header(uchar *pkt, struct in_addr dest, struct in_addr source,
+ u16 pkt_len, u8 proto)
+{
+ struct ip_udp_hdr *ip = (struct ip_udp_hdr *)pkt;
+
+ /*
+ * Construct an IP header.
+ */
+ /* IP_HDR_SIZE / 4 (not including UDP) */
+ ip->ip_hl_v = 0x45;
+ ip->ip_tos = 0;
+ ip->ip_len = htons(pkt_len);
+ ip->ip_p = proto;
+ ip->ip_id = htons(net_ip_id++);
+ ip->ip_off = htons(IP_FLAGS_DFRAG); /* Don't fragment */
+ ip->ip_ttl = 255;
+ ip->ip_sum = 0;
+ /* already in network byte order */
+ net_copy_ip((void *)&ip->ip_src, &source);
+ /* already in network byte order */
+ net_copy_ip((void *)&ip->ip_dst, &dest);
+
+ ip->ip_sum = compute_ip_checksum(ip, IP_HDR_SIZE);
+}
+
+void net_set_udp_header(uchar *pkt, struct in_addr dest, int dport, int sport,
+ int len)
+{
+ struct ip_udp_hdr *ip = (struct ip_udp_hdr *)pkt;
+
+ /*
+ * If the data is an odd number of bytes, zero the
+ * byte after the last byte so that the checksum
+ * will work.
+ */
+ if (len & 1)
+ pkt[IP_UDP_HDR_SIZE + len] = 0;
+
+ net_set_ip_header(pkt, dest, net_ip, IP_UDP_HDR_SIZE + len,
+ IPPROTO_UDP);
+
+ ip->udp_src = htons(sport);
+ ip->udp_dst = htons(dport);
+ ip->udp_len = htons(UDP_HDR_SIZE + len);
+ ip->udp_xsum = 0;
+}
+
+void copy_filename(char *dst, const char *src, int size)
+{
+ if (src && *src && (*src == '"')) {
+ ++src;
+ --size;
+ }
+
+ while ((--size > 0) && src && *src && (*src != '"'))
+ *dst++ = *src++;
+ *dst = '\0';
+}
+
+int is_serverip_in_cmd(void)
+{
+ return !!strchr(net_boot_file_name, ':');
+}
+
+int net_parse_bootfile(struct in_addr *ipaddr, char *filename, int max_len)
+{
+ char *colon;
+ struct in_addr ip;
+ ip.s_addr = 0;
+
+ if (net_boot_file_name[0] == '\0')
+ return 0;
+
+ colon = strchr(net_boot_file_name, ':');
+ if (colon) {
+ ip = string_to_ip(net_boot_file_name);
+ if (ipaddr && ip.s_addr)
+ *ipaddr = ip;
+ }
+ if (ip.s_addr) {
+ strncpy(filename, colon + 1, max_len);
+ } else {
+ strncpy(filename, net_boot_file_name, max_len);
+ }
+ filename[max_len - 1] = '\0';
+
+ return 1;
+}
+
+void ip_to_string(struct in_addr x, char *s)
+{
+ x.s_addr = ntohl(x.s_addr);
+ sprintf(s, "%d.%d.%d.%d",
+ (int) ((x.s_addr >> 24) & 0xff),
+ (int) ((x.s_addr >> 16) & 0xff),
+ (int) ((x.s_addr >> 8) & 0xff),
+ (int) ((x.s_addr >> 0) & 0xff)
+ );
+}
+
+void vlan_to_string(ushort x, char *s)
+{
+ x = ntohs(x);
+
+ if (x == (ushort)-1)
+ x = VLAN_NONE;
+
+ if (x == VLAN_NONE)
+ strcpy(s, "none");
+ else
+ sprintf(s, "%d", x & VLAN_IDMASK);
+}
+
+ushort string_to_vlan(const char *s)
+{
+ ushort id;
+
+ if (s == NULL)
+ return htons(VLAN_NONE);
+
+ if (*s < '0' || *s > '9')
+ id = VLAN_NONE;
+ else
+ id = (ushort)dectoul(s, NULL);
+
+ return htons(id);
+}
+
+ushort env_get_vlan(char *var)
+{
+ return string_to_vlan(env_get(var));
+}
diff --git a/net/net6.c b/net/net6.c
new file mode 100644
index 00000000000..4cff98df15c
--- /dev/null
+++ b/net/net6.c
@@ -0,0 +1,449 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Allied Telesis Labs NZ
+ * Chris Packham, <judge.packham@gmail.com>
+ *
+ * Copyright (C) 2022 YADRO
+ * Viacheslav Mitrofanov <v.v.mitrofanov@yadro.com>
+ */
+
+/* Simple IPv6 network layer implementation */
+
+#include <env_internal.h>
+#include <malloc.h>
+#include <net.h>
+#include <net6.h>
+#include <ndisc.h>
+#include <vsprintf.h>
+
+/* NULL IPv6 address */
+struct in6_addr const net_null_addr_ip6 = ZERO_IPV6_ADDR;
+/* Our gateway's IPv6 address */
+struct in6_addr net_gateway6 = ZERO_IPV6_ADDR;
+/* Our IPv6 addr (0 = unknown) */
+struct in6_addr net_ip6 = ZERO_IPV6_ADDR;
+/* Our link local IPv6 addr (0 = unknown) */
+struct in6_addr net_link_local_ip6 = ZERO_IPV6_ADDR;
+/* set server IPv6 addr (0 = unknown) */
+struct in6_addr net_server_ip6 = ZERO_IPV6_ADDR;
+/* The prefix length of our network */
+u32 net_prefix_length;
+
+bool use_ip6;
+
+static int on_ip6addr(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ char *mask;
+ size_t len;
+
+ if (flags & H_PROGRAMMATIC)
+ return 0;
+
+ if (op == env_op_delete) {
+ net_prefix_length = 0;
+ net_copy_ip6(&net_ip6, &net_null_addr_ip6);
+ return 0;
+ }
+
+ mask = strchr(value, '/');
+
+ if (mask) {
+ net_prefix_length = simple_strtoul(mask + 1, NULL, 10);
+ len = mask - value;
+ } else {
+ len = strlen(value);
+ }
+
+ return string_to_ip6(value, len, &net_ip6);
+}
+
+U_BOOT_ENV_CALLBACK(ip6addr, on_ip6addr);
+
+static int on_gatewayip6(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ if (flags & H_PROGRAMMATIC)
+ return 0;
+
+ return string_to_ip6(value, strlen(value), &net_gateway6);
+}
+
+U_BOOT_ENV_CALLBACK(gatewayip6, on_gatewayip6);
+
+static int on_serverip6(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ if (flags & H_PROGRAMMATIC)
+ return 0;
+
+ return string_to_ip6(value, strlen(value), &net_server_ip6);
+}
+
+U_BOOT_ENV_CALLBACK(serverip6, on_serverip6);
+
+int ip6_is_unspecified_addr(struct in6_addr *addr)
+{
+ return !(addr->s6_addr32[0] | addr->s6_addr32[1] |
+ addr->s6_addr32[2] | addr->s6_addr32[3]);
+}
+
+int ip6_is_our_addr(struct in6_addr *addr)
+{
+ return !memcmp(addr, &net_link_local_ip6, sizeof(struct in6_addr)) ||
+ !memcmp(addr, &net_ip6, sizeof(struct in6_addr));
+}
+
+void ip6_make_eui(unsigned char eui[8], unsigned char const enetaddr[6])
+{
+ memcpy(eui, enetaddr, 3);
+ memcpy(&eui[5], &enetaddr[3], 3);
+ eui[3] = 0xff;
+ eui[4] = 0xfe;
+ eui[0] ^= 2; /* "u" bit set to indicate global scope */
+}
+
+void ip6_make_lladdr(struct in6_addr *lladr, unsigned char const enetaddr[6])
+{
+ unsigned char eui[8];
+
+ memset(lladr, 0, sizeof(struct in6_addr));
+ lladr->s6_addr16[0] = htons(IPV6_LINK_LOCAL_PREFIX);
+ ip6_make_eui(eui, enetaddr);
+ memcpy(&lladr->s6_addr[8], eui, 8);
+}
+
+void ip6_make_snma(struct in6_addr *mcast_addr, struct in6_addr *ip6_addr)
+{
+ memset(mcast_addr, 0, sizeof(struct in6_addr));
+ mcast_addr->s6_addr[0] = 0xff;
+ mcast_addr->s6_addr[1] = IPV6_ADDRSCOPE_LINK;
+ mcast_addr->s6_addr[11] = 0x01;
+ mcast_addr->s6_addr[12] = 0xff;
+ mcast_addr->s6_addr[13] = ip6_addr->s6_addr[13];
+ mcast_addr->s6_addr[14] = ip6_addr->s6_addr[14];
+ mcast_addr->s6_addr[15] = ip6_addr->s6_addr[15];
+}
+
+void
+ip6_make_mult_ethdstaddr(unsigned char enetaddr[6], struct in6_addr *mcast_addr)
+{
+ enetaddr[0] = 0x33;
+ enetaddr[1] = 0x33;
+ memcpy(&enetaddr[2], &mcast_addr->s6_addr[12], 4);
+}
+
+int
+ip6_addr_in_subnet(struct in6_addr *our_addr, struct in6_addr *neigh_addr,
+ u32 plen)
+{
+ __be32 *addr_dwords;
+ __be32 *neigh_dwords;
+
+ addr_dwords = our_addr->s6_addr32;
+ neigh_dwords = neigh_addr->s6_addr32;
+
+ while (plen > 32) {
+ if (*addr_dwords++ != *neigh_dwords++)
+ return 0;
+
+ plen -= 32;
+ }
+
+ /* Check any remaining bits */
+ if (plen > 0) {
+ if ((*addr_dwords >> (32 - plen)) !=
+ (*neigh_dwords >> (32 - plen))) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static inline unsigned int csum_fold(unsigned int sum)
+{
+ sum = (sum & 0xffff) + (sum >> 16);
+ sum = (sum & 0xffff) + (sum >> 16);
+
+ /* Opaque moment. If reverse it to zero it will not be checked on
+ * receiver's side. It leads to bad negibour advertisement.
+ */
+ if (sum == 0xffff)
+ return sum;
+
+ return ~sum;
+}
+
+static inline unsigned short from32to16(unsigned int x)
+{
+ /* add up 16-bit and 16-bit for 16+c bit */
+ x = (x & 0xffff) + (x >> 16);
+ /* add up carry.. */
+ x = (x & 0xffff) + (x >> 16);
+ return x;
+}
+
+static u32 csum_do_csum(const u8 *buff, int len)
+{
+ int odd;
+ unsigned int result = 0;
+
+ if (len <= 0)
+ goto out;
+ odd = 1 & (unsigned long)buff;
+ if (odd) {
+#ifdef __LITTLE_ENDIAN
+ result += (*buff << 8);
+#else
+ result = *buff;
+#endif
+ len--;
+ buff++;
+ }
+ if (len >= 2) {
+ if (2 & (unsigned long)buff) {
+ result += *(unsigned short *)buff;
+ len -= 2;
+ buff += 2;
+ }
+ if (len >= 4) {
+ const unsigned char *end = buff + ((u32)len & ~3);
+ unsigned int carry = 0;
+
+ do {
+ unsigned int w = *(unsigned int *)buff;
+
+ buff += 4;
+ result += carry;
+ result += w;
+ carry = (w > result);
+ } while (buff < end);
+ result += carry;
+ result = (result & 0xffff) + (result >> 16);
+ }
+ if (len & 2) {
+ result += *(unsigned short *)buff;
+ buff += 2;
+ }
+ }
+ if (len & 1)
+#ifdef __LITTLE_ENDIAN
+ result += *buff;
+#else
+ result += (*buff << 8);
+#endif
+ result = from32to16(result);
+ if (odd)
+ result = ((result >> 8) & 0xff) | ((result & 0xff) << 8);
+out:
+ return result;
+}
+
+unsigned int csum_partial(const unsigned char *buff, int len, unsigned int sum)
+{
+ unsigned int result = csum_do_csum(buff, len);
+
+ /* add in old sum, and carry.. */
+ result += sum;
+ /* 16+c bits -> 16 bits */
+ result = (result & 0xffff) + (result >> 16);
+ return result;
+}
+
+unsigned short int
+csum_ipv6_magic(struct in6_addr *saddr, struct in6_addr *daddr, u16 len,
+ unsigned short proto, unsigned int csum)
+{
+ int carry;
+ u32 ulen;
+ u32 uproto;
+ u32 sum = csum;
+
+ sum += saddr->s6_addr32[0];
+ carry = (sum < saddr->s6_addr32[0]);
+ sum += carry;
+
+ sum += saddr->s6_addr32[1];
+ carry = (sum < saddr->s6_addr32[1]);
+ sum += carry;
+
+ sum += saddr->s6_addr32[2];
+ carry = (sum < saddr->s6_addr32[2]);
+ sum += carry;
+
+ sum += saddr->s6_addr32[3];
+ carry = (sum < saddr->s6_addr32[3]);
+ sum += carry;
+
+ sum += daddr->s6_addr32[0];
+ carry = (sum < daddr->s6_addr32[0]);
+ sum += carry;
+
+ sum += daddr->s6_addr32[1];
+ carry = (sum < daddr->s6_addr32[1]);
+ sum += carry;
+
+ sum += daddr->s6_addr32[2];
+ carry = (sum < daddr->s6_addr32[2]);
+ sum += carry;
+
+ sum += daddr->s6_addr32[3];
+ carry = (sum < daddr->s6_addr32[3]);
+ sum += carry;
+
+ ulen = htonl((u32)len);
+ sum += ulen;
+ carry = (sum < ulen);
+ sum += carry;
+
+ uproto = htonl(proto);
+ sum += uproto;
+ carry = (sum < uproto);
+ sum += carry;
+
+ return csum_fold(sum);
+}
+
+int ip6_add_hdr(uchar *xip, struct in6_addr *src, struct in6_addr *dest,
+ int nextheader, int hoplimit, int payload_len)
+{
+ struct ip6_hdr *ip6 = (struct ip6_hdr *)xip;
+
+ ip6->version = 6;
+ ip6->priority = 0;
+ ip6->flow_lbl[0] = 0;
+ ip6->flow_lbl[1] = 0;
+ ip6->flow_lbl[2] = 0;
+ ip6->payload_len = htons(payload_len);
+ ip6->nexthdr = nextheader;
+ ip6->hop_limit = hoplimit;
+ net_copy_ip6(&ip6->saddr, src);
+ net_copy_ip6(&ip6->daddr, dest);
+
+ return sizeof(struct ip6_hdr);
+}
+
+int net_send_udp_packet6(uchar *ether, struct in6_addr *dest, int dport,
+ int sport, int len)
+{
+ uchar *pkt;
+ struct udp_hdr *udp;
+ u16 csum_p;
+
+ udp = (struct udp_hdr *)((uchar *)net_tx_packet + net_eth_hdr_size() +
+ IP6_HDR_SIZE);
+
+ udp->udp_dst = htons(dport);
+ udp->udp_src = htons(sport);
+ udp->udp_len = htons(len + UDP_HDR_SIZE);
+
+ /* checksum */
+ udp->udp_xsum = 0;
+ csum_p = csum_partial((u8 *)udp, len + UDP_HDR_SIZE, 0);
+ udp->udp_xsum = csum_ipv6_magic(&net_ip6, dest, len + UDP_HDR_SIZE,
+ IPPROTO_UDP, csum_p);
+
+ /* if MAC address was not discovered yet, save the packet and do
+ * neighbour discovery
+ */
+ if (!memcmp(ether, net_null_ethaddr, 6)) {
+ net_copy_ip6(&net_nd_sol_packet_ip6, dest);
+ net_nd_packet_mac = ether;
+
+ pkt = net_nd_tx_packet;
+ pkt += net_set_ether(pkt, net_nd_packet_mac, PROT_IP6);
+ pkt += ip6_add_hdr(pkt, &net_ip6, dest, IPPROTO_UDP, 64,
+ len + UDP_HDR_SIZE);
+ memcpy(pkt, (uchar *)udp, len + UDP_HDR_SIZE);
+
+ /* size of the waiting packet */
+ net_nd_tx_packet_size = (pkt - net_nd_tx_packet) +
+ UDP_HDR_SIZE + len;
+
+ /* and do the neighbor solicitation */
+ net_nd_try = 1;
+ net_nd_timer_start = get_timer(0);
+ ndisc_request();
+ return 1; /* waiting */
+ }
+
+ pkt = (uchar *)net_tx_packet;
+ pkt += net_set_ether(pkt, ether, PROT_IP6);
+ pkt += ip6_add_hdr(pkt, &net_ip6, dest, IPPROTO_UDP, 64,
+ len + UDP_HDR_SIZE);
+ (void)eth_send(net_tx_packet, pkt - net_tx_packet + UDP_HDR_SIZE + len);
+
+ return 0; /* transmitted */
+}
+
+int net_ip6_handler(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len)
+{
+ struct in_addr zero_ip = {.s_addr = 0 };
+ struct icmp6hdr *icmp;
+ struct udp_hdr *udp;
+ u16 csum;
+ u16 csum_p;
+ u16 hlen;
+
+ if (len < IP6_HDR_SIZE)
+ return -EINVAL;
+
+ if (ip6->version != 6)
+ return -EINVAL;
+
+ switch (ip6->nexthdr) {
+ case PROT_ICMPV6:
+ icmp = (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE);
+ csum = icmp->icmp6_cksum;
+ hlen = ntohs(ip6->payload_len);
+ icmp->icmp6_cksum = 0;
+ /* checksum */
+ csum_p = csum_partial((u8 *)icmp, hlen, 0);
+ icmp->icmp6_cksum = csum_ipv6_magic(&ip6->saddr, &ip6->daddr,
+ hlen, PROT_ICMPV6, csum_p);
+
+ if (icmp->icmp6_cksum != csum)
+ return -EINVAL;
+
+ switch (icmp->icmp6_type) {
+ case IPV6_ICMP_ECHO_REQUEST:
+ case IPV6_ICMP_ECHO_REPLY:
+ ping6_receive(et, ip6, len);
+ break;
+ case IPV6_NDISC_NEIGHBOUR_SOLICITATION:
+ case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT:
+ case IPV6_NDISC_ROUTER_ADVERTISEMENT:
+ ndisc_receive(et, ip6, len);
+ break;
+ default:
+ break;
+ }
+ break;
+ case IPPROTO_UDP:
+ udp = (struct udp_hdr *)(((uchar *)ip6) + IP6_HDR_SIZE);
+ csum = udp->udp_xsum;
+ hlen = ntohs(ip6->payload_len);
+ udp->udp_xsum = 0;
+ /* checksum */
+ csum_p = csum_partial((u8 *)udp, hlen, 0);
+ udp->udp_xsum = csum_ipv6_magic(&ip6->saddr, &ip6->daddr,
+ hlen, IPPROTO_UDP, csum_p);
+
+ if (csum != udp->udp_xsum)
+ return -EINVAL;
+
+ /* IP header OK. Pass the packet to the current handler. */
+ net_get_udp_handler()((uchar *)ip6 + IP6_HDR_SIZE +
+ UDP_HDR_SIZE,
+ ntohs(udp->udp_dst),
+ zero_ip,
+ ntohs(udp->udp_src),
+ ntohs(udp->udp_len) - 8);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
diff --git a/net/net_rand.h b/net/net_rand.h
new file mode 100644
index 00000000000..686e85f2b53
--- /dev/null
+++ b/net/net_rand.h
@@ -0,0 +1,59 @@
+/*
+ * Copied from LiMon - BOOTP.
+ *
+ * Copyright 1994, 1995, 2000 Neil Russell.
+ * (See License)
+ * Copyright 2000 Paolo Scaffardi
+ */
+
+#ifndef __NET_RAND_H__
+#define __NET_RAND_H__
+
+#include <dm/uclass.h>
+#include <rng.h>
+
+/*
+ * Return a seed for the PRNG derived from the eth0 MAC address.
+ */
+static inline unsigned int seed_mac(void)
+{
+ unsigned char enetaddr[ARP_HLEN];
+ unsigned int seed;
+
+ /* get our mac */
+ memcpy(enetaddr, eth_get_ethaddr(), ARP_HLEN);
+
+ seed = enetaddr[5];
+ seed ^= enetaddr[4] << 8;
+ seed ^= enetaddr[3] << 16;
+ seed ^= enetaddr[2] << 24;
+ seed ^= enetaddr[1];
+ seed ^= enetaddr[0] << 8;
+
+ return seed;
+}
+
+/*
+ * Seed the random number generator using the eth0 MAC address.
+ */
+static inline void srand_mac(void)
+{
+ int ret;
+ struct udevice *devp;
+ u32 randv = 0;
+
+ if (CONFIG_IS_ENABLED(DM_RNG)) {
+ ret = uclass_get_device(UCLASS_RNG, 0, &devp);
+ if (ret) {
+ ret = dm_rng_read(devp, &randv, sizeof(randv));
+ if (ret < 0)
+ randv = 0;
+ }
+ }
+ if (randv)
+ srand(randv);
+ else
+ srand(seed_mac());
+}
+
+#endif /* __NET_RAND_H__ */
diff --git a/net/nfs.c b/net/nfs.c
new file mode 100644
index 00000000000..537d4c62de2
--- /dev/null
+++ b/net/nfs.c
@@ -0,0 +1,984 @@
+/*
+ * NFS support driver - based on etherboot and U-BOOT's tftp.c
+ *
+ * Masami Komiya <mkomiya@sonare.it> 2004
+ *
+ */
+
+/* NOTE: the NFS code is heavily inspired by the NetBSD netboot code (read:
+ * large portions are copied verbatim) as distributed in OSKit 0.97. A few
+ * changes were necessary to adapt the code to Etherboot and to fix several
+ * inconsistencies. Also the RPC message preparation is done "by hand" to
+ * avoid adding netsprintf() which I find hard to understand and use. */
+
+/* NOTE 2: Etherboot does not care about things beyond the kernel image, so
+ * it loads the kernel image off the boot server (ARP_SERVER) and does not
+ * access the client root disk (root-path in dhcpd.conf), which would use
+ * ARP_ROOTSERVER. The root disk is something the operating system we are
+ * about to load needs to use. This is different from the OSKit 0.97 logic. */
+
+/* NOTE 3: Symlink handling introduced by Anselm M Hoffmeister, 2003-July-14
+ * If a symlink is encountered, it is followed as far as possible (recursion
+ * possible, maximum 16 steps). There is no clearing of ".."'s inside the
+ * path, so please DON'T DO THAT. thx. */
+
+/* NOTE 4: NFSv3 support added by Guillaume GARDET, 2016-June-20.
+ * NFSv2 is still used by default. But if server does not support NFSv2, then
+ * NFSv3 is used, if available on NFS server. */
+
+/* NOTE 5: NFSv1 support added by Christian Gmeiner, Thomas Rienoessl,
+ * September 27, 2018. As of now, NFSv3 is the default choice. If the server
+ * does not support NFSv3, we fall back to versions 2 or 1. */
+
+#include <command.h>
+#include <display_options.h>
+#ifdef CONFIG_SYS_DIRECT_FLASH_NFS
+#include <flash.h>
+#endif
+#include <image.h>
+#include <log.h>
+#include <net.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include "nfs.h"
+#include "bootp.h"
+#include <time.h>
+
+#define HASHES_PER_LINE 65 /* Number of "loading" hashes per line */
+#define NFS_RETRY_COUNT 30
+
+#define NFS_RPC_ERR 1
+#define NFS_RPC_DROP 124
+
+static int fs_mounted;
+static unsigned long rpc_id;
+static int nfs_offset = -1;
+static int nfs_len;
+static const ulong nfs_timeout = CONFIG_NFS_TIMEOUT;
+
+static char dirfh[NFS3_FHSIZE]; /* NFSv2 / NFSv3 file handle of directory */
+static unsigned int dirfh3_length; /* (variable) length of dirfh when NFSv3 */
+static char filefh[NFS3_FHSIZE]; /* NFSv2 / NFSv3 file handle */
+static unsigned int filefh3_length; /* (variable) length of filefh when NFSv3 */
+
+static enum net_loop_state nfs_download_state;
+static struct in_addr nfs_server_ip;
+static int nfs_server_mount_port;
+static int nfs_server_port;
+static int nfs_our_port;
+static int nfs_timeout_count;
+static int nfs_state;
+#define STATE_PRCLOOKUP_PROG_MOUNT_REQ 1
+#define STATE_PRCLOOKUP_PROG_NFS_REQ 2
+#define STATE_MOUNT_REQ 3
+#define STATE_UMOUNT_REQ 4
+#define STATE_LOOKUP_REQ 5
+#define STATE_READ_REQ 6
+#define STATE_READLINK_REQ 7
+
+static char *nfs_filename;
+static char *nfs_path;
+static char nfs_path_buff[2048];
+
+enum nfs_version {
+ NFS_UNKOWN = 0,
+ NFS_V1 = 1,
+ NFS_V2 = 2,
+ NFS_V3 = 3,
+};
+
+static enum nfs_version choosen_nfs_version = NFS_V3;
+static inline int store_block(uchar *src, unsigned offset, unsigned len)
+{
+ ulong newsize = offset + len;
+#ifdef CONFIG_SYS_DIRECT_FLASH_NFS
+ int i, rc = 0;
+
+ for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) {
+ /* start address in flash? */
+ if (image_load_addr + offset >= flash_info[i].start[0]) {
+ rc = 1;
+ break;
+ }
+ }
+
+ if (rc) { /* Flash is destination for this packet */
+ rc = flash_write((uchar *)src, (ulong)image_load_addr + offset,
+ len);
+ if (rc) {
+ flash_perror(rc);
+ return -1;
+ }
+ } else
+#endif /* CONFIG_SYS_DIRECT_FLASH_NFS */
+ {
+ void *ptr = map_sysmem(image_load_addr + offset, len);
+
+ memcpy(ptr, src, len);
+ unmap_sysmem(ptr);
+ }
+
+ if (net_boot_file_size < (offset + len))
+ net_boot_file_size = newsize;
+ return 0;
+}
+
+static char *basename(char *path)
+{
+ char *fname;
+
+ fname = path + strlen(path) - 1;
+ while (fname >= path) {
+ if (*fname == '/') {
+ fname++;
+ break;
+ }
+ fname--;
+ }
+ return fname;
+}
+
+static char *dirname(char *path)
+{
+ char *fname;
+
+ fname = basename(path);
+ --fname;
+ *fname = '\0';
+ return path;
+}
+
+/**************************************************************************
+RPC_ADD_CREDENTIALS - Add RPC authentication/verifier entries
+**************************************************************************/
+static uint32_t *rpc_add_credentials(uint32_t *p)
+{
+ /* Here's the executive summary on authentication requirements of the
+ * various NFS server implementations: Linux accepts both AUTH_NONE
+ * and AUTH_UNIX authentication (also accepts an empty hostname field
+ * in the AUTH_UNIX scheme). *BSD refuses AUTH_NONE, but accepts
+ * AUTH_UNIX (also accepts an empty hostname field in the AUTH_UNIX
+ * scheme). To be safe, use AUTH_UNIX and pass the hostname if we have
+ * it (if the BOOTP/DHCP reply didn't give one, just use an empty
+ * hostname). */
+
+ /* Provide an AUTH_UNIX credential. */
+ *p++ = htonl(1); /* AUTH_UNIX */
+ *p++ = htonl(20); /* auth length */
+ *p++ = 0; /* stamp */
+ *p++ = 0; /* hostname string */
+ *p++ = 0; /* uid */
+ *p++ = 0; /* gid */
+ *p++ = 0; /* auxiliary gid list */
+
+ /* Provide an AUTH_NONE verifier. */
+ *p++ = 0; /* AUTH_NONE */
+ *p++ = 0; /* auth length */
+
+ return p;
+}
+
+/**************************************************************************
+RPC_LOOKUP - Lookup RPC Port numbers
+**************************************************************************/
+static void rpc_req(int rpc_prog, int rpc_proc, uint32_t *data, int datalen)
+{
+ struct rpc_t rpc_pkt;
+ unsigned long id;
+ uint32_t *p;
+ int pktlen;
+ int sport;
+
+ id = ++rpc_id;
+ rpc_pkt.u.call.id = htonl(id);
+ rpc_pkt.u.call.type = htonl(MSG_CALL);
+ rpc_pkt.u.call.rpcvers = htonl(2); /* use RPC version 2 */
+ rpc_pkt.u.call.prog = htonl(rpc_prog);
+ switch (rpc_prog) {
+ case PROG_NFS:
+ switch (choosen_nfs_version) {
+ case NFS_V1:
+ case NFS_V2:
+ rpc_pkt.u.call.vers = htonl(2);
+ break;
+
+ case NFS_V3:
+ rpc_pkt.u.call.vers = htonl(3);
+ break;
+
+ case NFS_UNKOWN:
+ /* nothing to do */
+ break;
+ }
+ break;
+ case PROG_MOUNT:
+ switch (choosen_nfs_version) {
+ case NFS_V1:
+ rpc_pkt.u.call.vers = htonl(1);
+ break;
+
+ case NFS_V2:
+ rpc_pkt.u.call.vers = htonl(2);
+ break;
+
+ case NFS_V3:
+ rpc_pkt.u.call.vers = htonl(3);
+ break;
+
+ case NFS_UNKOWN:
+ /* nothing to do */
+ break;
+ }
+ break;
+ case PROG_PORTMAP:
+ default:
+ rpc_pkt.u.call.vers = htonl(2); /* portmapper is version 2 */
+ }
+ rpc_pkt.u.call.proc = htonl(rpc_proc);
+ p = rpc_pkt.u.call.data;
+
+ if (datalen)
+ memcpy(p, data, datalen * sizeof(uint32_t));
+
+ pktlen = (char *)p + datalen * sizeof(uint32_t) - (char *)&rpc_pkt;
+
+ memcpy((char *)net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE,
+ &rpc_pkt.u.data[0], pktlen);
+
+ if (rpc_prog == PROG_PORTMAP)
+ sport = SUNRPC_PORT;
+ else if (rpc_prog == PROG_MOUNT)
+ sport = nfs_server_mount_port;
+ else
+ sport = nfs_server_port;
+
+ net_send_udp_packet(net_server_ethaddr, nfs_server_ip, sport,
+ nfs_our_port, pktlen);
+}
+
+/**************************************************************************
+RPC_LOOKUP - Lookup RPC Port numbers
+**************************************************************************/
+static void rpc_lookup_req(int prog, int ver)
+{
+ uint32_t data[16];
+
+ data[0] = 0; data[1] = 0; /* auth credential */
+ data[2] = 0; data[3] = 0; /* auth verifier */
+ data[4] = htonl(prog);
+ data[5] = htonl(ver);
+ data[6] = htonl(17); /* IP_UDP */
+ data[7] = 0;
+ rpc_req(PROG_PORTMAP, PORTMAP_GETPORT, data, 8);
+}
+
+/**************************************************************************
+NFS_MOUNT - Mount an NFS Filesystem
+**************************************************************************/
+static void nfs_mount_req(char *path)
+{
+ uint32_t data[1024];
+ uint32_t *p;
+ int len;
+ int pathlen;
+
+ pathlen = strlen(path);
+
+ p = &(data[0]);
+ p = rpc_add_credentials(p);
+
+ *p++ = htonl(pathlen);
+ if (pathlen & 3)
+ *(p + pathlen / 4) = 0;
+ memcpy(p, path, pathlen);
+ p += (pathlen + 3) / 4;
+
+ len = (uint32_t *)p - (uint32_t *)&(data[0]);
+
+ rpc_req(PROG_MOUNT, MOUNT_ADDENTRY, data, len);
+}
+
+/**************************************************************************
+NFS_UMOUNTALL - Unmount all our NFS Filesystems on the Server
+**************************************************************************/
+static void nfs_umountall_req(void)
+{
+ uint32_t data[1024];
+ uint32_t *p;
+ int len;
+
+ if ((nfs_server_mount_port == -1) || (!fs_mounted))
+ /* Nothing mounted, nothing to umount */
+ return;
+
+ p = &(data[0]);
+ p = rpc_add_credentials(p);
+
+ len = (uint32_t *)p - (uint32_t *)&(data[0]);
+
+ rpc_req(PROG_MOUNT, MOUNT_UMOUNTALL, data, len);
+}
+
+/***************************************************************************
+ * NFS_READLINK (AH 2003-07-14)
+ * This procedure is called when read of the first block fails -
+ * this probably happens when it's a directory or a symlink
+ * In case of successful readlink(), the dirname is manipulated,
+ * so that inside the nfs() function a recursion can be done.
+ **************************************************************************/
+static void nfs_readlink_req(void)
+{
+ uint32_t data[1024];
+ uint32_t *p;
+ int len;
+
+ p = &(data[0]);
+ p = rpc_add_credentials(p);
+
+ if (choosen_nfs_version != NFS_V3) {
+ memcpy(p, filefh, NFS_FHSIZE);
+ p += (NFS_FHSIZE / 4);
+ } else { /* NFS_V3 */
+ *p++ = htonl(filefh3_length);
+ memcpy(p, filefh, filefh3_length);
+ p += (filefh3_length / 4);
+ }
+
+ len = (uint32_t *)p - (uint32_t *)&(data[0]);
+
+ rpc_req(PROG_NFS, NFS_READLINK, data, len);
+}
+
+/**************************************************************************
+NFS_LOOKUP - Lookup Pathname
+**************************************************************************/
+static void nfs_lookup_req(char *fname)
+{
+ uint32_t data[1024];
+ uint32_t *p;
+ int len;
+ int fnamelen;
+
+ fnamelen = strlen(fname);
+
+ p = &(data[0]);
+ p = rpc_add_credentials(p);
+
+ if (choosen_nfs_version != NFS_V3) {
+ memcpy(p, dirfh, NFS_FHSIZE);
+ p += (NFS_FHSIZE / 4);
+ *p++ = htonl(fnamelen);
+ if (fnamelen & 3)
+ *(p + fnamelen / 4) = 0;
+ memcpy(p, fname, fnamelen);
+ p += (fnamelen + 3) / 4;
+
+ len = (uint32_t *)p - (uint32_t *)&(data[0]);
+
+ rpc_req(PROG_NFS, NFS_LOOKUP, data, len);
+ } else { /* NFS_V3 */
+ *p++ = htonl(dirfh3_length); /* Dir handle length */
+ memcpy(p, dirfh, dirfh3_length);
+ p += (dirfh3_length / 4);
+ *p++ = htonl(fnamelen);
+ if (fnamelen & 3)
+ *(p + fnamelen / 4) = 0;
+ memcpy(p, fname, fnamelen);
+ p += (fnamelen + 3) / 4;
+
+ len = (uint32_t *)p - (uint32_t *)&(data[0]);
+
+ rpc_req(PROG_NFS, NFS3PROC_LOOKUP, data, len);
+ }
+}
+
+/**************************************************************************
+NFS_READ - Read File on NFS Server
+**************************************************************************/
+static void nfs_read_req(int offset, int readlen)
+{
+ uint32_t data[1024];
+ uint32_t *p;
+ int len;
+
+ p = &(data[0]);
+ p = rpc_add_credentials(p);
+
+ if (choosen_nfs_version != NFS_V3) {
+ memcpy(p, filefh, NFS_FHSIZE);
+ p += (NFS_FHSIZE / 4);
+ *p++ = htonl(offset);
+ *p++ = htonl(readlen);
+ *p++ = 0;
+ } else { /* NFS_V3 */
+ *p++ = htonl(filefh3_length);
+ memcpy(p, filefh, filefh3_length);
+ p += (filefh3_length / 4);
+ *p++ = htonl(0); /* offset is 64-bit long, so fill with 0 */
+ *p++ = htonl(offset);
+ *p++ = htonl(readlen);
+ *p++ = 0;
+ }
+
+ len = (uint32_t *)p - (uint32_t *)&(data[0]);
+
+ rpc_req(PROG_NFS, NFS_READ, data, len);
+}
+
+/**************************************************************************
+RPC request dispatcher
+**************************************************************************/
+static void nfs_send(void)
+{
+ debug("%s\n", __func__);
+
+ switch (nfs_state) {
+ case STATE_PRCLOOKUP_PROG_MOUNT_REQ:
+ if (choosen_nfs_version != NFS_V3)
+ rpc_lookup_req(PROG_MOUNT, 1);
+ else /* NFS_V3 */
+ rpc_lookup_req(PROG_MOUNT, 3);
+ break;
+ case STATE_PRCLOOKUP_PROG_NFS_REQ:
+ if (choosen_nfs_version != NFS_V3)
+ rpc_lookup_req(PROG_NFS, 2);
+ else /* NFS_V3 */
+ rpc_lookup_req(PROG_NFS, 3);
+ break;
+ case STATE_MOUNT_REQ:
+ nfs_mount_req(nfs_path);
+ break;
+ case STATE_UMOUNT_REQ:
+ nfs_umountall_req();
+ break;
+ case STATE_LOOKUP_REQ:
+ nfs_lookup_req(nfs_filename);
+ break;
+ case STATE_READ_REQ:
+ nfs_read_req(nfs_offset, nfs_len);
+ break;
+ case STATE_READLINK_REQ:
+ nfs_readlink_req();
+ break;
+ }
+}
+
+/**************************************************************************
+Handlers for the reply from server
+**************************************************************************/
+
+static int rpc_handle_error(struct rpc_t *rpc_pkt)
+{
+ if (rpc_pkt->u.reply.rstatus ||
+ rpc_pkt->u.reply.verifier ||
+ rpc_pkt->u.reply.astatus ||
+ rpc_pkt->u.reply.data[0]) {
+ switch (ntohl(rpc_pkt->u.reply.astatus)) {
+ case NFS_RPC_SUCCESS: /* Not an error */
+ break;
+ case NFS_RPC_PROG_MISMATCH: {
+ /* Remote can't support NFS version */
+ const int min = ntohl(rpc_pkt->u.reply.data[0]);
+ const int max = ntohl(rpc_pkt->u.reply.data[1]);
+
+ if (max < NFS_V1 || max > NFS_V3 || min > NFS_V3) {
+ puts("*** ERROR: NFS version not supported");
+ debug(": Requested: V%d, accepted: min V%d - max V%d\n",
+ choosen_nfs_version,
+ ntohl(rpc_pkt->u.reply.data[0]),
+ ntohl(rpc_pkt->u.reply.data[1]));
+ puts("\n");
+ choosen_nfs_version = NFS_UNKOWN;
+ break;
+ }
+
+ debug("*** Warning: NFS version not supported: Requested: V%d, accepted: min V%d - max V%d\n",
+ choosen_nfs_version,
+ ntohl(rpc_pkt->u.reply.data[0]),
+ ntohl(rpc_pkt->u.reply.data[1]));
+ debug("Will retry with NFSv%d\n", min);
+ choosen_nfs_version = min;
+ return -NFS_RPC_PROG_MISMATCH;
+ }
+ case NFS_RPC_PROG_UNAVAIL:
+ case NFS_RPC_PROC_UNAVAIL:
+ case NFS_RPC_GARBAGE_ARGS:
+ case NFS_RPC_SYSTEM_ERR:
+ default: /* Unknown error on 'accept state' flag */
+ debug("*** ERROR: accept state error (%d)\n",
+ ntohl(rpc_pkt->u.reply.astatus));
+ break;
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+static int rpc_lookup_reply(int prog, uchar *pkt, unsigned len)
+{
+ struct rpc_t rpc_pkt;
+
+ memcpy(&rpc_pkt.u.data[0], pkt, len);
+
+ debug("%s\n", __func__);
+
+ if (ntohl(rpc_pkt.u.reply.id) > rpc_id)
+ return -NFS_RPC_ERR;
+ else if (ntohl(rpc_pkt.u.reply.id) < rpc_id)
+ return -NFS_RPC_DROP;
+
+ if (rpc_pkt.u.reply.rstatus ||
+ rpc_pkt.u.reply.verifier ||
+ rpc_pkt.u.reply.astatus)
+ return -1;
+
+ switch (prog) {
+ case PROG_MOUNT:
+ nfs_server_mount_port = ntohl(rpc_pkt.u.reply.data[0]);
+ break;
+ case PROG_NFS:
+ nfs_server_port = ntohl(rpc_pkt.u.reply.data[0]);
+ break;
+ }
+
+ return 0;
+}
+
+static int nfs_mount_reply(uchar *pkt, unsigned len)
+{
+ struct rpc_t rpc_pkt;
+ int ret;
+
+ debug("%s\n", __func__);
+
+ memcpy(&rpc_pkt.u.data[0], pkt, len);
+
+ if (ntohl(rpc_pkt.u.reply.id) > rpc_id)
+ return -NFS_RPC_ERR;
+ else if (ntohl(rpc_pkt.u.reply.id) < rpc_id)
+ return -NFS_RPC_DROP;
+
+ ret = rpc_handle_error(&rpc_pkt);
+ if (ret)
+ return ret;
+
+ fs_mounted = 1;
+ /* NFSv2 and NFSv3 use same structure */
+ if (choosen_nfs_version != NFS_V3) {
+ memcpy(dirfh, rpc_pkt.u.reply.data + 1, NFS_FHSIZE);
+ } else {
+ dirfh3_length = ntohl(rpc_pkt.u.reply.data[1]);
+ if (dirfh3_length > NFS3_FHSIZE)
+ dirfh3_length = NFS3_FHSIZE;
+ memcpy(dirfh, rpc_pkt.u.reply.data + 2, dirfh3_length);
+ }
+
+ return 0;
+}
+
+static int nfs_umountall_reply(uchar *pkt, unsigned len)
+{
+ struct rpc_t rpc_pkt;
+
+ debug("%s\n", __func__);
+
+ memcpy(&rpc_pkt.u.data[0], pkt, len);
+
+ if (ntohl(rpc_pkt.u.reply.id) > rpc_id)
+ return -NFS_RPC_ERR;
+ else if (ntohl(rpc_pkt.u.reply.id) < rpc_id)
+ return -NFS_RPC_DROP;
+
+ if (rpc_pkt.u.reply.rstatus ||
+ rpc_pkt.u.reply.verifier ||
+ rpc_pkt.u.reply.astatus)
+ return -1;
+
+ fs_mounted = 0;
+ memset(dirfh, 0, sizeof(dirfh));
+
+ return 0;
+}
+
+static int nfs_lookup_reply(uchar *pkt, unsigned len)
+{
+ struct rpc_t rpc_pkt;
+ int ret;
+
+ debug("%s\n", __func__);
+
+ memcpy(&rpc_pkt.u.data[0], pkt, len);
+
+ if (ntohl(rpc_pkt.u.reply.id) > rpc_id)
+ return -NFS_RPC_ERR;
+ else if (ntohl(rpc_pkt.u.reply.id) < rpc_id)
+ return -NFS_RPC_DROP;
+
+ ret = rpc_handle_error(&rpc_pkt);
+ if (ret)
+ return ret;
+
+ if (choosen_nfs_version != NFS_V3) {
+ if (((uchar *)&(rpc_pkt.u.reply.data[0]) - (uchar *)(&rpc_pkt) + NFS_FHSIZE) > len)
+ return -NFS_RPC_DROP;
+ memcpy(filefh, rpc_pkt.u.reply.data + 1, NFS_FHSIZE);
+ } else { /* NFS_V3 */
+ filefh3_length = ntohl(rpc_pkt.u.reply.data[1]);
+ if (filefh3_length > NFS3_FHSIZE)
+ filefh3_length = NFS3_FHSIZE;
+ memcpy(filefh, rpc_pkt.u.reply.data + 2, filefh3_length);
+ }
+
+ return 0;
+}
+
+static int nfs3_get_attributes_offset(uint32_t *data)
+{
+ if (data[1]) {
+ /* 'attributes_follow' flag is TRUE,
+ * so we have attributes on 21 dwords */
+ /* Skip unused values :
+ type; 32 bits value,
+ mode; 32 bits value,
+ nlink; 32 bits value,
+ uid; 32 bits value,
+ gid; 32 bits value,
+ size; 64 bits value,
+ used; 64 bits value,
+ rdev; 64 bits value,
+ fsid; 64 bits value,
+ fileid; 64 bits value,
+ atime; 64 bits value,
+ mtime; 64 bits value,
+ ctime; 64 bits value,
+ */
+ return 22;
+ } else {
+ /* 'attributes_follow' flag is FALSE,
+ * so we don't have any attributes */
+ return 1;
+ }
+}
+
+static int nfs_readlink_reply(uchar *pkt, unsigned len)
+{
+ struct rpc_t rpc_pkt;
+ int rlen;
+ int nfsv3_data_offset = 0;
+
+ debug("%s\n", __func__);
+
+ memcpy((unsigned char *)&rpc_pkt, pkt, len);
+
+ if (ntohl(rpc_pkt.u.reply.id) > rpc_id)
+ return -NFS_RPC_ERR;
+ else if (ntohl(rpc_pkt.u.reply.id) < rpc_id)
+ return -NFS_RPC_DROP;
+
+ if (rpc_pkt.u.reply.rstatus ||
+ rpc_pkt.u.reply.verifier ||
+ rpc_pkt.u.reply.astatus ||
+ rpc_pkt.u.reply.data[0])
+ return -1;
+
+ if (choosen_nfs_version == NFS_V3) {
+ nfsv3_data_offset =
+ nfs3_get_attributes_offset(rpc_pkt.u.reply.data);
+ }
+
+ /* new path length */
+ rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]);
+
+ if (((uchar *)&(rpc_pkt.u.reply.data[0]) - (uchar *)(&rpc_pkt) + rlen) > len)
+ return -NFS_RPC_DROP;
+
+ if (*((char *)&(rpc_pkt.u.reply.data[2 + nfsv3_data_offset])) != '/') {
+ int pathlen;
+
+ strcat(nfs_path, "/");
+ pathlen = strlen(nfs_path);
+ memcpy(nfs_path + pathlen,
+ (uchar *)&(rpc_pkt.u.reply.data[2 + nfsv3_data_offset]),
+ rlen);
+ nfs_path[pathlen + rlen] = 0;
+ } else {
+ memcpy(nfs_path,
+ (uchar *)&(rpc_pkt.u.reply.data[2 + nfsv3_data_offset]),
+ rlen);
+ nfs_path[rlen] = 0;
+ }
+ return 0;
+}
+
+static int nfs_read_reply(uchar *pkt, unsigned len)
+{
+ struct rpc_t rpc_pkt;
+ int rlen;
+ uchar *data_ptr;
+
+ debug("%s\n", __func__);
+
+ memcpy(&rpc_pkt.u.data[0], pkt, sizeof(rpc_pkt.u.reply));
+
+ if (ntohl(rpc_pkt.u.reply.id) > rpc_id)
+ return -NFS_RPC_ERR;
+ else if (ntohl(rpc_pkt.u.reply.id) < rpc_id)
+ return -NFS_RPC_DROP;
+
+ if (rpc_pkt.u.reply.rstatus ||
+ rpc_pkt.u.reply.verifier ||
+ rpc_pkt.u.reply.astatus ||
+ rpc_pkt.u.reply.data[0]) {
+ if (rpc_pkt.u.reply.rstatus)
+ return -9999;
+ if (rpc_pkt.u.reply.astatus)
+ return -9999;
+ return -ntohl(rpc_pkt.u.reply.data[0]);
+ }
+
+ if ((nfs_offset != 0) && !((nfs_offset) %
+ (NFS_READ_SIZE / 2 * 10 * HASHES_PER_LINE)))
+ puts("\n\t ");
+ if (!(nfs_offset % ((NFS_READ_SIZE / 2) * 10)))
+ putc('#');
+
+ if (choosen_nfs_version != NFS_V3) {
+ rlen = ntohl(rpc_pkt.u.reply.data[18]);
+ data_ptr = (uchar *)&(rpc_pkt.u.reply.data[19]);
+ } else { /* NFS_V3 */
+ int nfsv3_data_offset =
+ nfs3_get_attributes_offset(rpc_pkt.u.reply.data);
+
+ /* count value */
+ rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]);
+ /* Skip unused values :
+ EOF: 32 bits value,
+ data_size: 32 bits value,
+ */
+ data_ptr = (uchar *)
+ &(rpc_pkt.u.reply.data[4 + nfsv3_data_offset]);
+ }
+
+ if (((uchar *)&(rpc_pkt.u.reply.data[0]) - (uchar *)(&rpc_pkt) + rlen) > len)
+ return -9999;
+
+ if (store_block(data_ptr, nfs_offset, rlen))
+ return -9999;
+
+ return rlen;
+}
+
+/**************************************************************************
+Interfaces of U-BOOT
+**************************************************************************/
+static void nfs_timeout_handler(void)
+{
+ if (++nfs_timeout_count > NFS_RETRY_COUNT) {
+ puts("\nRetry count exceeded; starting again\n");
+ net_start_again();
+ } else {
+ puts("T ");
+ net_set_timeout_handler(nfs_timeout +
+ nfs_timeout * nfs_timeout_count,
+ nfs_timeout_handler);
+ nfs_send();
+ }
+}
+
+static void nfs_handler(uchar *pkt, unsigned dest, struct in_addr sip,
+ unsigned src, unsigned len)
+{
+ int rlen;
+ int reply;
+
+ debug("%s\n", __func__);
+
+ if (len > sizeof(struct rpc_t))
+ return;
+
+ if (dest != nfs_our_port)
+ return;
+
+ switch (nfs_state) {
+ case STATE_PRCLOOKUP_PROG_MOUNT_REQ:
+ if (rpc_lookup_reply(PROG_MOUNT, pkt, len) == -NFS_RPC_DROP)
+ break;
+ nfs_state = STATE_PRCLOOKUP_PROG_NFS_REQ;
+ nfs_send();
+ break;
+
+ case STATE_PRCLOOKUP_PROG_NFS_REQ:
+ if (rpc_lookup_reply(PROG_NFS, pkt, len) == -NFS_RPC_DROP)
+ break;
+ nfs_state = STATE_MOUNT_REQ;
+ nfs_send();
+ break;
+
+ case STATE_MOUNT_REQ:
+ reply = nfs_mount_reply(pkt, len);
+ if (reply == -NFS_RPC_DROP) {
+ break;
+ } else if (reply == -NFS_RPC_ERR) {
+ puts("*** ERROR: Cannot mount\n");
+ /* just to be sure... */
+ nfs_state = STATE_UMOUNT_REQ;
+ nfs_send();
+ } else if (reply == -NFS_RPC_PROG_MISMATCH &&
+ choosen_nfs_version != NFS_UNKOWN) {
+ nfs_state = STATE_MOUNT_REQ;
+ nfs_send();
+ } else {
+ nfs_state = STATE_LOOKUP_REQ;
+ nfs_send();
+ }
+ break;
+
+ case STATE_UMOUNT_REQ:
+ reply = nfs_umountall_reply(pkt, len);
+ if (reply == -NFS_RPC_DROP) {
+ break;
+ } else if (reply == -NFS_RPC_ERR) {
+ debug("*** ERROR: Cannot umount\n");
+ net_set_state(NETLOOP_FAIL);
+ } else {
+ puts("\ndone\n");
+ net_set_state(nfs_download_state);
+ }
+ break;
+
+ case STATE_LOOKUP_REQ:
+ reply = nfs_lookup_reply(pkt, len);
+ if (reply == -NFS_RPC_DROP) {
+ break;
+ } else if (reply == -NFS_RPC_ERR) {
+ puts("*** ERROR: File lookup fail\n");
+ nfs_state = STATE_UMOUNT_REQ;
+ nfs_send();
+ } else if (reply == -NFS_RPC_PROG_MISMATCH &&
+ choosen_nfs_version != NFS_UNKOWN) {
+ /* umount */
+ nfs_state = STATE_UMOUNT_REQ;
+ nfs_send();
+ /* And retry with another supported version */
+ nfs_state = STATE_PRCLOOKUP_PROG_MOUNT_REQ;
+ nfs_send();
+ } else {
+ nfs_state = STATE_READ_REQ;
+ nfs_offset = 0;
+ nfs_len = NFS_READ_SIZE;
+ nfs_send();
+ }
+ break;
+
+ case STATE_READLINK_REQ:
+ reply = nfs_readlink_reply(pkt, len);
+ if (reply == -NFS_RPC_DROP) {
+ break;
+ } else if (reply == -NFS_RPC_ERR) {
+ puts("*** ERROR: Symlink fail\n");
+ nfs_state = STATE_UMOUNT_REQ;
+ nfs_send();
+ } else {
+ debug("Symlink --> %s\n", nfs_path);
+ nfs_filename = basename(nfs_path);
+ nfs_path = dirname(nfs_path);
+
+ nfs_state = STATE_MOUNT_REQ;
+ nfs_send();
+ }
+ break;
+
+ case STATE_READ_REQ:
+ rlen = nfs_read_reply(pkt, len);
+ if (rlen == -NFS_RPC_DROP)
+ break;
+ net_set_timeout_handler(nfs_timeout, nfs_timeout_handler);
+ if (rlen > 0) {
+ nfs_offset += rlen;
+ nfs_send();
+ } else if ((rlen == -NFSERR_ISDIR) || (rlen == -NFSERR_INVAL)) {
+ /* symbolic link */
+ nfs_state = STATE_READLINK_REQ;
+ nfs_send();
+ } else {
+ if (!rlen)
+ nfs_download_state = NETLOOP_SUCCESS;
+ if (rlen < 0)
+ debug("NFS READ error (%d)\n", rlen);
+ nfs_state = STATE_UMOUNT_REQ;
+ nfs_send();
+ }
+ break;
+ }
+}
+
+void nfs_start(void)
+{
+ debug("%s\n", __func__);
+ nfs_download_state = NETLOOP_FAIL;
+
+ nfs_server_ip = net_server_ip;
+ nfs_path = (char *)nfs_path_buff;
+
+ if (nfs_path == NULL) {
+ net_set_state(NETLOOP_FAIL);
+ printf("*** ERROR: Fail allocate memory\n");
+ return;
+ }
+
+ if (!net_parse_bootfile(&nfs_server_ip, nfs_path,
+ sizeof(nfs_path_buff))) {
+ sprintf(nfs_path, "/nfsroot/%02X%02X%02X%02X.img",
+ net_ip.s_addr & 0xFF,
+ (net_ip.s_addr >> 8) & 0xFF,
+ (net_ip.s_addr >> 16) & 0xFF,
+ (net_ip.s_addr >> 24) & 0xFF);
+
+ printf("*** Warning: no boot file name; using '%s'\n",
+ nfs_path);
+ }
+
+ nfs_filename = basename(nfs_path);
+ nfs_path = dirname(nfs_path);
+
+ printf("Using %s device\n", eth_get_name());
+
+ printf("File transfer via NFS from server %pI4; our IP address is %pI4",
+ &nfs_server_ip, &net_ip);
+
+ /* Check if we need to send across this subnet */
+ if (net_gateway.s_addr && net_netmask.s_addr) {
+ struct in_addr our_net;
+ struct in_addr server_net;
+
+ our_net.s_addr = net_ip.s_addr & net_netmask.s_addr;
+ server_net.s_addr = nfs_server_ip.s_addr & net_netmask.s_addr;
+ if (our_net.s_addr != server_net.s_addr)
+ printf("; sending through gateway %pI4",
+ &net_gateway);
+ }
+ printf("\nFilename '%s/%s'.", nfs_path, nfs_filename);
+
+ if (net_boot_file_expected_size_in_blocks) {
+ printf(" Size is 0x%x Bytes = ",
+ net_boot_file_expected_size_in_blocks << 9);
+ print_size(net_boot_file_expected_size_in_blocks << 9, "");
+ }
+ printf("\nLoad address: 0x%lx\nLoading: *\b", image_load_addr);
+
+ net_set_timeout_handler(nfs_timeout, nfs_timeout_handler);
+ net_set_udp_handler(nfs_handler);
+
+ nfs_timeout_count = 0;
+ nfs_state = STATE_PRCLOOKUP_PROG_MOUNT_REQ;
+
+ /*nfs_our_port = 4096 + (get_ticks() % 3072);*/
+ /*FIX ME !!!*/
+ nfs_our_port = 1000;
+
+ /* zero out server ether in case the server ip has changed */
+ memset(net_server_ethaddr, 0, 6);
+
+ nfs_send();
+}
diff --git a/net/nfs.h b/net/nfs.h
new file mode 100644
index 00000000000..6bf1cb76bd5
--- /dev/null
+++ b/net/nfs.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Masami Komiya <mkomiya@sonare.it> 2004
+ */
+
+#ifndef __NFS_H__
+#define __NFS_H__
+
+#define SUNRPC_PORT 111
+
+#define PROG_PORTMAP 100000
+#define PROG_NFS 100003
+#define PROG_MOUNT 100005
+
+#define MSG_CALL 0
+#define MSG_REPLY 1
+
+#define PORTMAP_GETPORT 3
+
+#define MOUNT_ADDENTRY 1
+#define MOUNT_UMOUNTALL 4
+
+#define NFS_LOOKUP 4
+#define NFS_READLINK 5
+#define NFS_READ 6
+
+#define NFS3PROC_LOOKUP 3
+
+#define NFS_FHSIZE 32
+#define NFS3_FHSIZE 64
+
+#define NFSERR_PERM 1
+#define NFSERR_NOENT 2
+#define NFSERR_ACCES 13
+#define NFSERR_ISDIR 21
+#define NFSERR_INVAL 22
+
+/*
+ * Block size used for NFS read accesses. A RPC reply packet (including all
+ * headers) must fit within a single Ethernet frame to avoid fragmentation.
+ * However, if CONFIG_IP_DEFRAG is set, a bigger value could be used. In any
+ * case, most NFS servers are optimized for a power of 2.
+ */
+#define NFS_READ_SIZE 1024 /* biggest power of two that fits Ether frame */
+#define NFS_MAX_ATTRS 26
+
+/* Values for Accept State flag on RPC answers (See: rfc1831) */
+enum rpc_accept_stat {
+ NFS_RPC_SUCCESS = 0, /* RPC executed successfully */
+ NFS_RPC_PROG_UNAVAIL = 1, /* remote hasn't exported program */
+ NFS_RPC_PROG_MISMATCH = 2, /* remote can't support version # */
+ NFS_RPC_PROC_UNAVAIL = 3, /* program can't support procedure */
+ NFS_RPC_GARBAGE_ARGS = 4, /* procedure can't decode params */
+ NFS_RPC_SYSTEM_ERR = 5 /* errors like memory allocation failure */
+};
+
+struct rpc_t {
+ union {
+ uint8_t data[NFS_READ_SIZE + (6 + NFS_MAX_ATTRS) *
+ sizeof(uint32_t)];
+ struct {
+ uint32_t id;
+ uint32_t type;
+ uint32_t rpcvers;
+ uint32_t prog;
+ uint32_t vers;
+ uint32_t proc;
+ uint32_t data[1];
+ } call;
+ struct {
+ uint32_t id;
+ uint32_t type;
+ uint32_t rstatus;
+ uint32_t verifier;
+ uint32_t v2;
+ uint32_t astatus;
+ uint32_t data[NFS_READ_SIZE / sizeof(uint32_t) +
+ NFS_MAX_ATTRS];
+ } reply;
+ } u;
+};
+void nfs_start(void); /* Begin NFS */
+
+/**********************************************************************/
+
+#endif /* __NFS_H__ */
diff --git a/net/pcap.c b/net/pcap.c
new file mode 100644
index 00000000000..c959e3e4e51
--- /dev/null
+++ b/net/pcap.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 Ramon Fried <rfried.dev@gmail.com>
+ */
+
+#include <net.h>
+#include <net/pcap.h>
+#include <time.h>
+#include <linux/errno.h>
+#include <asm/io.h>
+
+#define LINKTYPE_ETHERNET 1
+
+static bool initialized;
+static bool running;
+static bool buffer_full;
+static void *buf;
+static unsigned int max_size;
+static unsigned int pos;
+
+static unsigned long incoming_count;
+static unsigned long outgoing_count;
+
+struct pcap_header {
+ u32 magic;
+ u16 version_major;
+ u16 version_minor;
+ s32 thiszone;
+ u32 sigfigs;
+ u32 snaplen;
+ u32 network;
+};
+
+struct pcap_packet_header {
+ u32 ts_sec;
+ u32 ts_usec;
+ u32 incl_len;
+ u32 orig_len;
+};
+
+static struct pcap_header file_header = {
+ .magic = 0xa1b2c3d4,
+ .version_major = 2,
+ .version_minor = 4,
+ .snaplen = 65535,
+ .network = LINKTYPE_ETHERNET,
+};
+
+int pcap_init(phys_addr_t paddr, unsigned long size)
+{
+ buf = map_physmem(paddr, size, 0);
+ if (!buf) {
+ printf("Failed mapping PCAP memory\n");
+ return -ENOMEM;
+ }
+
+ printf("PCAP capture initialized: addr: 0x%lx max length: %lu\n",
+ (unsigned long)buf, size);
+
+ memcpy(buf, &file_header, sizeof(file_header));
+ pos = sizeof(file_header);
+ max_size = size;
+ initialized = true;
+ running = false;
+ buffer_full = false;
+ incoming_count = 0;
+ outgoing_count = 0;
+ return 0;
+}
+
+int pcap_start_stop(bool start)
+{
+ if (!initialized) {
+ printf("error: pcap was not initialized\n");
+ return -ENODEV;
+ }
+
+ running = start;
+
+ return 0;
+}
+
+int pcap_clear(void)
+{
+ if (!initialized) {
+ printf("error: pcap was not initialized\n");
+ return -ENODEV;
+ }
+
+ pos = sizeof(file_header);
+ incoming_count = 0;
+ outgoing_count = 0;
+ buffer_full = false;
+
+ printf("pcap capture cleared\n");
+ return 0;
+}
+
+int pcap_post(const void *packet, size_t len, bool outgoing)
+{
+ struct pcap_packet_header header;
+ u64 cur_time = timer_get_us();
+
+ if (!initialized || !running || !buf)
+ return -ENODEV;
+
+ if (buffer_full)
+ return -ENOMEM;
+
+ if ((pos + len + sizeof(header)) >= max_size) {
+ buffer_full = true;
+ printf("\n!!! Buffer is full, consider increasing buffer size !!!\n");
+ return -ENOMEM;
+ }
+
+ header.ts_sec = cur_time / 1000000;
+ header.ts_usec = cur_time % 1000000;
+ header.incl_len = len;
+ header.orig_len = len;
+
+ memcpy(buf + pos, &header, sizeof(header));
+ pos += sizeof(header);
+ memcpy(buf + pos, packet, len);
+ pos += len;
+
+ if (outgoing)
+ outgoing_count++;
+ else
+ incoming_count++;
+
+ env_set_hex("pcapsize", pos);
+
+ return 0;
+}
+
+int pcap_print_status(void)
+{
+ if (!initialized) {
+ printf("pcap was not initialized\n");
+ return -ENODEV;
+ }
+ printf("PCAP status:\n");
+ printf("\tInitialized addr: 0x%lx\tmax length: %u\n",
+ (unsigned long)buf, max_size);
+ printf("\tStatus: %s.\t file size: %u\n", running ? "Active" : "Idle",
+ pos);
+ printf("\tIncoming packets: %lu Outgoing packets: %lu\n",
+ incoming_count, outgoing_count);
+
+ return 0;
+}
+
+bool pcap_active(void)
+{
+ return running;
+}
diff --git a/net/ping.c b/net/ping.c
new file mode 100644
index 00000000000..075df3663fe
--- /dev/null
+++ b/net/ping.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copied from Linux Monitor (LiMon) - Networking.
+ *
+ * Copyright 1994 - 2000 Neil Russell.
+ * (See License)
+ * Copyright 2000 Roland Borde
+ * Copyright 2000 Paolo Scaffardi
+ * Copyright 2000-2002 Wolfgang Denk, wd@denx.de
+ */
+
+#include "ping.h"
+#include "arp.h"
+#include <log.h>
+#include <net.h>
+
+static ushort ping_seq_number;
+
+/* The ip address to ping */
+struct in_addr net_ping_ip;
+
+static void set_icmp_header(uchar *pkt, struct in_addr dest)
+{
+ /*
+ * Construct an IP and ICMP header.
+ */
+ struct icmp_hdr *icmp = (struct icmp_hdr *)(pkt + IP_HDR_SIZE);
+
+ net_set_ip_header(pkt, dest, net_ip, IP_ICMP_HDR_SIZE, IPPROTO_ICMP);
+
+ icmp->type = ICMP_ECHO_REQUEST;
+ icmp->code = 0;
+ icmp->checksum = 0;
+ icmp->un.echo.id = 0;
+ icmp->un.echo.sequence = htons(ping_seq_number++);
+ icmp->checksum = compute_ip_checksum(icmp, ICMP_HDR_SIZE);
+}
+
+static int ping_send(void)
+{
+ uchar *pkt;
+ int eth_hdr_size;
+
+ /* XXX always send arp request */
+
+ debug_cond(DEBUG_DEV_PKT, "sending ARP for %pI4\n", &net_ping_ip);
+
+ net_arp_wait_packet_ip = net_ping_ip;
+
+ eth_hdr_size = net_set_ether(net_tx_packet, net_null_ethaddr, PROT_IP);
+ pkt = (uchar *)net_tx_packet + eth_hdr_size;
+
+ set_icmp_header(pkt, net_ping_ip);
+
+ /* size of the waiting packet */
+ arp_wait_tx_packet_size = eth_hdr_size + IP_ICMP_HDR_SIZE;
+
+ /* and do the ARP request */
+ arp_wait_try = 1;
+ arp_wait_timer_start = get_timer(0);
+ arp_request();
+ return 1; /* waiting */
+}
+
+static void ping_timeout_handler(void)
+{
+ eth_halt();
+ net_set_state(NETLOOP_FAIL); /* we did not get the reply */
+}
+
+void ping_start(void)
+{
+ printf("Using %s device\n", eth_get_name());
+ net_set_timeout_handler(10000UL, ping_timeout_handler);
+
+ ping_send();
+}
+
+void ping_receive(struct ethernet_hdr *et, struct ip_udp_hdr *ip, int len)
+{
+ struct icmp_hdr *icmph = (struct icmp_hdr *)&ip->udp_src;
+ struct in_addr src_ip;
+ int eth_hdr_size;
+ uchar *tx_packet;
+
+ switch (icmph->type) {
+ case ICMP_ECHO_REPLY:
+ src_ip = net_read_ip((void *)&ip->ip_src);
+ if (src_ip.s_addr == net_ping_ip.s_addr)
+ net_set_state(NETLOOP_SUCCESS);
+ return;
+ case ICMP_ECHO_REQUEST:
+ if (net_ip.s_addr == 0)
+ return;
+
+ eth_hdr_size = net_update_ether(et, et->et_src, PROT_IP);
+
+ debug_cond(DEBUG_DEV_PKT,
+ "Got ICMP ECHO REQUEST, return %d bytes\n",
+ eth_hdr_size + len);
+
+ ip->ip_sum = 0;
+ ip->ip_off = 0;
+ net_copy_ip((void *)&ip->ip_dst, &ip->ip_src);
+ net_copy_ip((void *)&ip->ip_src, &net_ip);
+ ip->ip_sum = compute_ip_checksum(ip, IP_HDR_SIZE);
+
+ icmph->type = ICMP_ECHO_REPLY;
+ icmph->checksum = 0;
+ icmph->checksum = compute_ip_checksum(icmph, len - IP_HDR_SIZE);
+
+ tx_packet = net_get_async_tx_pkt_buf();
+ memcpy(tx_packet, et, eth_hdr_size + len);
+ net_send_packet(tx_packet, eth_hdr_size + len);
+ return;
+/* default:
+ return;*/
+ }
+}
diff --git a/net/ping.h b/net/ping.h
new file mode 100644
index 00000000000..76ac225fc07
--- /dev/null
+++ b/net/ping.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copied from Linux Monitor (LiMon) - Networking.
+ *
+ * Copyright 1994 - 2000 Neil Russell.
+ * (See License)
+ * Copyright 2000 Roland Borde
+ * Copyright 2000 Paolo Scaffardi
+ * Copyright 2000-2002 Wolfgang Denk, wd@denx.de
+ */
+
+#ifndef __PING_H__
+#define __PING_H__
+
+#include <net.h>
+
+/*
+ * Initialize ping (beginning of netloop)
+ */
+void ping_start(void);
+
+/*
+ * Deal with the receipt of a ping packet
+ *
+ * @param et Ethernet header in packet
+ * @param ip IP header in the same packet
+ * @param len Packet length
+ */
+void ping_receive(struct ethernet_hdr *et, struct ip_udp_hdr *ip, int len);
+
+#endif /* __PING_H__ */
diff --git a/net/ping6.c b/net/ping6.c
new file mode 100644
index 00000000000..2479e08fd82
--- /dev/null
+++ b/net/ping6.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Allied Telesis Labs NZ
+ * Chris Packham, <judge.packham@gmail.com>
+ *
+ * Copyright (C) 2022 YADRO
+ * Viacheslav Mitrofanov <v.v.mitrofanov@yadro.com>
+ */
+
+/* Simple ping6 implementation */
+
+#include <net.h>
+#include <net6.h>
+#include "ndisc.h"
+
+static ushort seq_no;
+
+/* the ipv6 address to ping */
+struct in6_addr net_ping_ip6;
+
+int
+ip6_make_ping(uchar *eth_dst_addr, struct in6_addr *neigh_addr, uchar *pkt)
+{
+ struct echo_msg *msg;
+ u16 len;
+ u16 csum_p;
+ uchar *pkt_old = pkt;
+
+ len = sizeof(struct echo_msg);
+
+ pkt += net_set_ether(pkt, eth_dst_addr, PROT_IP6);
+ pkt += ip6_add_hdr(pkt, &net_ip6, neigh_addr, PROT_ICMPV6,
+ IPV6_NDISC_HOPLIMIT, len);
+
+ /* ICMPv6 - Echo */
+ msg = (struct echo_msg *)pkt;
+ msg->icmph.icmp6_type = IPV6_ICMP_ECHO_REQUEST;
+ msg->icmph.icmp6_code = 0;
+ msg->icmph.icmp6_cksum = 0;
+ msg->icmph.icmp6_identifier = 0;
+ msg->icmph.icmp6_sequence = htons(seq_no++);
+ msg->id = msg->icmph.icmp6_identifier; /* these seem redundant */
+ msg->sequence = msg->icmph.icmp6_sequence;
+
+ /* checksum */
+ csum_p = csum_partial((u8 *)msg, len, 0);
+ msg->icmph.icmp6_cksum = csum_ipv6_magic(&net_ip6, neigh_addr, len,
+ PROT_ICMPV6, csum_p);
+
+ pkt += len;
+
+ return pkt - pkt_old;
+}
+
+int ping6_send(void)
+{
+ uchar *pkt;
+ static uchar mac[6];
+
+ /* always send neighbor solicit */
+
+ memcpy(mac, net_null_ethaddr, 6);
+
+ net_nd_sol_packet_ip6 = net_ping_ip6;
+ net_nd_packet_mac = mac;
+
+ pkt = net_nd_tx_packet;
+ pkt += ip6_make_ping(mac, &net_ping_ip6, pkt);
+
+ /* size of the waiting packet */
+ net_nd_tx_packet_size = (pkt - net_nd_tx_packet);
+
+ /* and do the ARP request */
+ net_nd_try = 1;
+ net_nd_timer_start = get_timer(0);
+ ndisc_request();
+ return 1; /* waiting */
+}
+
+static void ping6_timeout(void)
+{
+ eth_halt();
+ net_set_state(NETLOOP_FAIL); /* we did not get the reply */
+}
+
+void ping6_start(void)
+{
+ printf("Using %s device\n", eth_get_name());
+ net_set_timeout_handler(10000UL, ping6_timeout);
+
+ ping6_send();
+}
+
+int ping6_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len)
+{
+ struct icmp6hdr *icmp =
+ (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE);
+ struct in6_addr src_ip;
+
+ switch (icmp->icmp6_type) {
+ case IPV6_ICMP_ECHO_REPLY:
+ src_ip = ip6->saddr;
+ if (memcmp(&net_ping_ip6, &src_ip, sizeof(struct in6_addr)))
+ return -EINVAL;
+ net_set_state(NETLOOP_SUCCESS);
+ break;
+ case IPV6_ICMP_ECHO_REQUEST:
+ /* ignore for now.... */
+ debug("Got ICMPv6 ECHO REQUEST from %pI6c\n", &ip6->saddr);
+ return -EINVAL;
+ default:
+ debug("Unexpected ICMPv6 type 0x%x\n", icmp->icmp6_type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
diff --git a/net/rarp.c b/net/rarp.c
new file mode 100644
index 00000000000..a346e067cb9
--- /dev/null
+++ b/net/rarp.c
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2000-2002
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ */
+
+#include <command.h>
+#include <log.h>
+#include <net.h>
+#include <net/tftp.h>
+#include "nfs.h"
+#include "bootp.h"
+#include "rarp.h"
+
+#define TIMEOUT 5000UL /* Milliseconds before trying BOOTP again */
+
+int rarp_try;
+
+/*
+ * Handle a RARP received packet.
+ */
+void rarp_receive(struct ip_udp_hdr *ip, unsigned len)
+{
+ struct arp_hdr *arp;
+
+ debug_cond(DEBUG_NET_PKT, "Got RARP\n");
+ arp = (struct arp_hdr *)ip;
+ if (len < ARP_HDR_SIZE) {
+ printf("bad length %d < %d\n", len, ARP_HDR_SIZE);
+ return;
+ }
+
+ if ((ntohs(arp->ar_op) != RARPOP_REPLY) ||
+ (ntohs(arp->ar_hrd) != ARP_ETHER) ||
+ (ntohs(arp->ar_pro) != PROT_IP) ||
+ (arp->ar_hln != 6) || (arp->ar_pln != 4)) {
+ puts("invalid RARP header\n");
+ } else {
+ net_copy_ip(&net_ip, &arp->ar_data[16]);
+ if (net_server_ip.s_addr == 0)
+ net_copy_ip(&net_server_ip, &arp->ar_data[6]);
+ memcpy(net_server_ethaddr, &arp->ar_data[0], 6);
+ debug_cond(DEBUG_DEV_PKT, "Got good RARP\n");
+ net_auto_load();
+ }
+}
+
+/*
+ * Timeout on BOOTP request.
+ */
+static void rarp_timeout_handler(void)
+{
+ if (rarp_try >= CONFIG_NET_RETRY_COUNT) {
+ puts("\nRetry count exceeded; starting again\n");
+ net_start_again();
+ } else {
+ net_set_timeout_handler(TIMEOUT, rarp_timeout_handler);
+ rarp_request();
+ }
+}
+
+void rarp_request(void)
+{
+ uchar *pkt;
+ struct arp_hdr *rarp;
+ int eth_hdr_size;
+
+ printf("RARP broadcast %d\n", ++rarp_try);
+ pkt = net_tx_packet;
+
+ eth_hdr_size = net_set_ether(pkt, net_bcast_ethaddr, PROT_RARP);
+ pkt += eth_hdr_size;
+
+ rarp = (struct arp_hdr *)pkt;
+
+ rarp->ar_hrd = htons(ARP_ETHER);
+ rarp->ar_pro = htons(PROT_IP);
+ rarp->ar_hln = 6;
+ rarp->ar_pln = 4;
+ rarp->ar_op = htons(RARPOP_REQUEST);
+ memcpy(&rarp->ar_data[0], net_ethaddr, 6); /* source ET addr */
+ memcpy(&rarp->ar_data[6], &net_ip, 4); /* source IP addr */
+ /* dest ET addr = source ET addr ??*/
+ memcpy(&rarp->ar_data[10], net_ethaddr, 6);
+ /* dest IP addr set to broadcast */
+ memset(&rarp->ar_data[16], 0xff, 4);
+
+ net_send_packet(net_tx_packet, eth_hdr_size + ARP_HDR_SIZE);
+
+ net_set_timeout_handler(TIMEOUT, rarp_timeout_handler);
+}
diff --git a/net/rarp.h b/net/rarp.h
new file mode 100644
index 00000000000..de4504e5d83
--- /dev/null
+++ b/net/rarp.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Copyright 2000
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ */
+
+#if defined(CONFIG_CMD_RARP)
+
+#ifndef __RARP_H__
+#define __RARP_H__
+
+#include <net.h>
+
+/**********************************************************************/
+/*
+ * Global functions and variables.
+ */
+
+extern int rarp_try;
+
+/* Process the receipt of a RARP packet */
+void rarp_receive(struct ip_udp_hdr *ip, unsigned len);
+void rarp_request(void); /* Send a RARP request */
+
+/**********************************************************************/
+
+#endif /* __RARP_H__ */
+#endif
diff --git a/net/sntp.c b/net/sntp.c
new file mode 100644
index 00000000000..73d1d87d38b
--- /dev/null
+++ b/net/sntp.c
@@ -0,0 +1,123 @@
+/*
+ * SNTP support driver
+ *
+ * Masami Komiya <mkomiya@sonare.it> 2005
+ *
+ */
+
+#include <command.h>
+#include <dm.h>
+#include <log.h>
+#include <net.h>
+#include <rtc.h>
+
+#include <net/sntp.h>
+
+#define SNTP_TIMEOUT 10000UL
+
+static int sntp_our_port;
+
+/* NTP server IP address */
+struct in_addr net_ntp_server;
+/* offset time from UTC */
+int net_ntp_time_offset;
+
+static void sntp_send(void)
+{
+ struct sntp_pkt_t pkt;
+ int pktlen = SNTP_PACKET_LEN;
+ int sport;
+
+ debug("%s\n", __func__);
+
+ memset(&pkt, 0, sizeof(pkt));
+
+ pkt.li = NTP_LI_NOLEAP;
+ pkt.vn = NTP_VERSION;
+ pkt.mode = NTP_MODE_CLIENT;
+
+ memcpy((char *)net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE,
+ (char *)&pkt, pktlen);
+
+ sntp_our_port = 10000 + (get_timer(0) % 4096);
+ sport = NTP_SERVICE_PORT;
+
+ net_send_udp_packet(net_server_ethaddr, net_ntp_server, sport,
+ sntp_our_port, pktlen);
+}
+
+static void sntp_timeout_handler(void)
+{
+ puts("Timeout\n");
+ net_set_state(NETLOOP_FAIL);
+ return;
+}
+
+static void sntp_handler(uchar *pkt, unsigned dest, struct in_addr sip,
+ unsigned src, unsigned len)
+{
+ struct sntp_pkt_t *rpktp = (struct sntp_pkt_t *)pkt;
+ struct rtc_time tm;
+ ulong seconds;
+
+ debug("%s\n", __func__);
+
+ if (dest != sntp_our_port)
+ return;
+
+ /*
+ * As the RTC's used in U-Boot support second resolution only
+ * we simply ignore the sub-second field.
+ */
+ memcpy(&seconds, &rpktp->transmit_timestamp, sizeof(ulong));
+
+ rtc_to_tm(ntohl(seconds) - 2208988800UL + net_ntp_time_offset, &tm);
+#ifdef CONFIG_DM_RTC
+ struct udevice *dev;
+ int ret;
+
+ ret = uclass_get_device(UCLASS_RTC, 0, &dev);
+ if (ret)
+ printf("SNTP: cannot find RTC: err=%d\n", ret);
+ else
+ dm_rtc_set(dev, &tm);
+#elif defined(CONFIG_CMD_DATE)
+ rtc_set(&tm);
+#endif
+ printf("Date: %4d-%02d-%02d Time: %2d:%02d:%02d\n",
+ tm.tm_year, tm.tm_mon, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+
+ net_set_state(NETLOOP_SUCCESS);
+}
+
+/*
+ * SNTP:
+ *
+ * Prerequisites: - own ethernet address
+ * - own IP address
+ * We want: - network time
+ * Next step: none
+ */
+int sntp_prereq(void *data)
+{
+ if (net_ntp_server.s_addr == 0) {
+ puts("*** ERROR: NTP server address not given\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+int sntp_start(void *data)
+{
+ debug("%s\n", __func__);
+
+ net_set_timeout_handler(SNTP_TIMEOUT, sntp_timeout_handler);
+ net_set_udp_handler(sntp_handler);
+ memset(net_server_ethaddr, 0, sizeof(net_server_ethaddr));
+
+ sntp_send();
+
+ return 0;
+}
diff --git a/net/tcp.c b/net/tcp.c
new file mode 100644
index 00000000000..b0cc8a1fe3e
--- /dev/null
+++ b/net/tcp.c
@@ -0,0 +1,718 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2017 Duncan Hare, all rights reserved.
+ */
+
+/*
+ * General Desription:
+ *
+ * TCP support for the wget command, for fast file downloading.
+ *
+ * HTTP/TCP Receiver:
+ *
+ * Prerequisites: - own ethernet address
+ * - own IP address
+ * - Server IP address
+ * - Server with TCP
+ * - TCP application (eg wget)
+ * Next Step HTTPS?
+ */
+#include <command.h>
+#include <console.h>
+#include <env_internal.h>
+#include <errno.h>
+#include <net.h>
+#include <net/tcp.h>
+
+/*
+ * TCP sliding window control used by us to request re-TX
+ */
+static struct tcp_sack_v tcp_lost;
+
+/* TCP option timestamp */
+static u32 loc_timestamp;
+static u32 rmt_timestamp;
+
+static u32 tcp_seq_init;
+static u32 tcp_ack_edge;
+
+static int tcp_activity_count;
+
+/*
+ * Search for TCP_SACK and review the comments before the code section
+ * TCP_SACK is the number of packets at the front of the stream
+ */
+
+enum pkt_state {PKT, NOPKT};
+struct sack_r {
+ struct sack_edges se;
+ enum pkt_state st;
+};
+
+static struct sack_r edge_a[TCP_SACK];
+static unsigned int sack_idx;
+static unsigned int prev_len;
+
+/*
+ * TCP lengths are stored as a rounded up number of 32 bit words.
+ * Add 3 to length round up, rounded, then divided into the
+ * length in 32 bit words.
+ */
+#define LEN_B_TO_DW(x) ((x) >> 2)
+#define ROUND_TCPHDR_LEN(x) (LEN_B_TO_DW((x) + 3))
+#define SHIFT_TO_TCPHDRLEN_FIELD(x) ((x) << 4)
+#define GET_TCP_HDR_LEN_IN_BYTES(x) ((x) >> 2)
+
+/* TCP connection state */
+static enum tcp_state current_tcp_state;
+
+/* Current TCP RX packet handler */
+static rxhand_tcp *tcp_packet_handler;
+
+/**
+ * tcp_get_tcp_state() - get current TCP state
+ *
+ * Return: Current TCP state
+ */
+enum tcp_state tcp_get_tcp_state(void)
+{
+ return current_tcp_state;
+}
+
+/**
+ * tcp_set_tcp_state() - set current TCP state
+ * @new_state: new TCP state
+ */
+void tcp_set_tcp_state(enum tcp_state new_state)
+{
+ current_tcp_state = new_state;
+}
+
+static void dummy_handler(uchar *pkt, u16 dport,
+ struct in_addr sip, u16 sport,
+ u32 tcp_seq_num, u32 tcp_ack_num,
+ u8 action, unsigned int len)
+{
+}
+
+/**
+ * tcp_set_tcp_handler() - set a handler to receive data
+ * @f: handler
+ */
+void tcp_set_tcp_handler(rxhand_tcp *f)
+{
+ debug_cond(DEBUG_INT_STATE, "--- net_loop TCP handler set (%p)\n", f);
+ if (!f)
+ tcp_packet_handler = dummy_handler;
+ else
+ tcp_packet_handler = f;
+}
+
+/**
+ * tcp_set_pseudo_header() - set TCP pseudo header
+ * @pkt: the packet
+ * @src: source IP address
+ * @dest: destinaion IP address
+ * @tcp_len: tcp length
+ * @pkt_len: packet length
+ *
+ * Return: the checksum of the packet
+ */
+u16 tcp_set_pseudo_header(uchar *pkt, struct in_addr src, struct in_addr dest,
+ int tcp_len, int pkt_len)
+{
+ union tcp_build_pkt *b = (union tcp_build_pkt *)pkt;
+ int checksum_len;
+
+ /*
+ * Pseudo header
+ *
+ * Zero the byte after the last byte so that the header checksum
+ * will always work.
+ */
+ pkt[pkt_len] = 0;
+
+ net_copy_ip((void *)&b->ph.p_src, &src);
+ net_copy_ip((void *)&b->ph.p_dst, &dest);
+ b->ph.rsvd = 0;
+ b->ph.p = IPPROTO_TCP;
+ b->ph.len = htons(tcp_len);
+ checksum_len = tcp_len + PSEUDO_HDR_SIZE;
+
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP Pesudo Header (to=%pI4, from=%pI4, Len=%d)\n",
+ &b->ph.p_dst, &b->ph.p_src, checksum_len);
+
+ return compute_ip_checksum(pkt + PSEUDO_PAD_SIZE, checksum_len);
+}
+
+/**
+ * net_set_ack_options() - set TCP options in acknowledge packets
+ * @b: the packet
+ *
+ * Return: TCP header length
+ */
+int net_set_ack_options(union tcp_build_pkt *b)
+{
+ b->sack.hdr.tcp_hlen = SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
+
+ b->sack.t_opt.kind = TCP_O_TS;
+ b->sack.t_opt.len = TCP_OPT_LEN_A;
+ b->sack.t_opt.t_snd = htons(loc_timestamp);
+ b->sack.t_opt.t_rcv = rmt_timestamp;
+ b->sack.sack_v.kind = TCP_1_NOP;
+ b->sack.sack_v.len = 0;
+
+ if (IS_ENABLED(CONFIG_PROT_TCP_SACK)) {
+ if (tcp_lost.len > TCP_OPT_LEN_2) {
+ debug_cond(DEBUG_DEV_PKT, "TCP ack opt lost.len %x\n",
+ tcp_lost.len);
+ b->sack.sack_v.len = tcp_lost.len;
+ b->sack.sack_v.kind = TCP_V_SACK;
+ b->sack.sack_v.hill[0].l = htonl(tcp_lost.hill[0].l);
+ b->sack.sack_v.hill[0].r = htonl(tcp_lost.hill[0].r);
+
+ /*
+ * These SACK structures are initialized with NOPs to
+ * provide TCP header alignment padding. There are 4
+ * SACK structures used for both header padding and
+ * internally.
+ */
+ b->sack.sack_v.hill[1].l = htonl(tcp_lost.hill[1].l);
+ b->sack.sack_v.hill[1].r = htonl(tcp_lost.hill[1].r);
+ b->sack.sack_v.hill[2].l = htonl(tcp_lost.hill[2].l);
+ b->sack.sack_v.hill[2].r = htonl(tcp_lost.hill[2].r);
+ b->sack.sack_v.hill[3].l = TCP_O_NOP;
+ b->sack.sack_v.hill[3].r = TCP_O_NOP;
+ }
+
+ b->sack.hdr.tcp_hlen = SHIFT_TO_TCPHDRLEN_FIELD(ROUND_TCPHDR_LEN(TCP_HDR_SIZE +
+ TCP_TSOPT_SIZE +
+ tcp_lost.len));
+ } else {
+ b->sack.sack_v.kind = 0;
+ b->sack.hdr.tcp_hlen = SHIFT_TO_TCPHDRLEN_FIELD(ROUND_TCPHDR_LEN(TCP_HDR_SIZE +
+ TCP_TSOPT_SIZE));
+ }
+
+ /*
+ * This returns the actual rounded up length of the
+ * TCP header to add to the total packet length
+ */
+
+ return GET_TCP_HDR_LEN_IN_BYTES(b->sack.hdr.tcp_hlen);
+}
+
+/**
+ * net_set_ack_options() - set TCP options in SYN packets
+ * @b: the packet
+ */
+void net_set_syn_options(union tcp_build_pkt *b)
+{
+ if (IS_ENABLED(CONFIG_PROT_TCP_SACK))
+ tcp_lost.len = 0;
+
+ b->ip.hdr.tcp_hlen = 0xa0;
+
+ b->ip.mss.kind = TCP_O_MSS;
+ b->ip.mss.len = TCP_OPT_LEN_4;
+ b->ip.mss.mss = htons(TCP_MSS);
+ b->ip.scale.kind = TCP_O_SCL;
+ b->ip.scale.scale = TCP_SCALE;
+ b->ip.scale.len = TCP_OPT_LEN_3;
+ if (IS_ENABLED(CONFIG_PROT_TCP_SACK)) {
+ b->ip.sack_p.kind = TCP_P_SACK;
+ b->ip.sack_p.len = TCP_OPT_LEN_2;
+ } else {
+ b->ip.sack_p.kind = TCP_1_NOP;
+ b->ip.sack_p.len = TCP_1_NOP;
+ }
+ b->ip.t_opt.kind = TCP_O_TS;
+ b->ip.t_opt.len = TCP_OPT_LEN_A;
+ loc_timestamp = get_ticks();
+ rmt_timestamp = 0;
+ b->ip.t_opt.t_snd = 0;
+ b->ip.t_opt.t_rcv = 0;
+ b->ip.end = TCP_O_END;
+}
+
+int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len,
+ u8 action, u32 tcp_seq_num, u32 tcp_ack_num)
+{
+ union tcp_build_pkt *b = (union tcp_build_pkt *)pkt;
+ int pkt_hdr_len;
+ int pkt_len;
+ int tcp_len;
+
+ /*
+ * Header: 5 32 bit words. 4 bits TCP header Length,
+ * 4 bits reserved options
+ */
+ b->ip.hdr.tcp_flags = action;
+ pkt_hdr_len = IP_TCP_HDR_SIZE;
+ b->ip.hdr.tcp_hlen = SHIFT_TO_TCPHDRLEN_FIELD(LEN_B_TO_DW(TCP_HDR_SIZE));
+
+ switch (action) {
+ case TCP_SYN:
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP Hdr:SYN (%pI4, %pI4, sq=%u, ak=%u)\n",
+ &net_server_ip, &net_ip,
+ tcp_seq_num, tcp_ack_num);
+ tcp_activity_count = 0;
+ net_set_syn_options(b);
+ tcp_seq_num = 0;
+ tcp_ack_num = 0;
+ pkt_hdr_len = IP_TCP_O_SIZE;
+ if (current_tcp_state == TCP_SYN_SENT) { /* Too many SYNs */
+ action = TCP_FIN;
+ current_tcp_state = TCP_FIN_WAIT_1;
+ } else {
+ current_tcp_state = TCP_SYN_SENT;
+ }
+ break;
+ case TCP_SYN | TCP_ACK:
+ case TCP_ACK:
+ pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(b);
+ b->ip.hdr.tcp_flags = action;
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP Hdr:ACK (%pI4, %pI4, s=%u, a=%u, A=%x)\n",
+ &net_server_ip, &net_ip, tcp_seq_num, tcp_ack_num,
+ action);
+ break;
+ case TCP_FIN:
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP Hdr:FIN (%pI4, %pI4, s=%u, a=%u)\n",
+ &net_server_ip, &net_ip, tcp_seq_num, tcp_ack_num);
+ payload_len = 0;
+ pkt_hdr_len = IP_TCP_HDR_SIZE;
+ current_tcp_state = TCP_FIN_WAIT_1;
+ break;
+ case TCP_RST | TCP_ACK:
+ case TCP_RST:
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP Hdr:RST (%pI4, %pI4, s=%u, a=%u)\n",
+ &net_server_ip, &net_ip, tcp_seq_num, tcp_ack_num);
+ current_tcp_state = TCP_CLOSED;
+ break;
+ /* Notify connection closing */
+ case (TCP_FIN | TCP_ACK):
+ case (TCP_FIN | TCP_ACK | TCP_PUSH):
+ if (current_tcp_state == TCP_CLOSE_WAIT)
+ current_tcp_state = TCP_CLOSING;
+
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP Hdr:FIN ACK PSH(%pI4, %pI4, s=%u, a=%u, A=%x)\n",
+ &net_server_ip, &net_ip,
+ tcp_seq_num, tcp_ack_num, action);
+ fallthrough;
+ default:
+ pkt_hdr_len = IP_HDR_SIZE + net_set_ack_options(b);
+ b->ip.hdr.tcp_flags = action | TCP_PUSH | TCP_ACK;
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP Hdr:dft (%pI4, %pI4, s=%u, a=%u, A=%x)\n",
+ &net_server_ip, &net_ip,
+ tcp_seq_num, tcp_ack_num, action);
+ }
+
+ pkt_len = pkt_hdr_len + payload_len;
+ tcp_len = pkt_len - IP_HDR_SIZE;
+
+ tcp_ack_edge = tcp_ack_num;
+ /* TCP Header */
+ b->ip.hdr.tcp_ack = htonl(tcp_ack_edge);
+ b->ip.hdr.tcp_src = htons(sport);
+ b->ip.hdr.tcp_dst = htons(dport);
+ b->ip.hdr.tcp_seq = htonl(tcp_seq_num);
+
+ /*
+ * TCP window size - TCP header variable tcp_win.
+ * Change tcp_win only if you have an understanding of network
+ * overrun, congestion, TCP segment sizes, TCP windows, TCP scale,
+ * queuing theory and packet buffering. If there are too few buffers,
+ * there will be data loss, recovery may work or the sending TCP,
+ * the server, could abort the stream transmission.
+ * MSS is governed by maximum Ethernet frame length.
+ * The number of buffers is governed by the desire to have a queue of
+ * full buffers to be processed at the destination to maximize
+ * throughput. Temporary memory use for the boot phase on modern
+ * SOCs is may not be considered a constraint to buffer space, if
+ * it is, then the u-boot tftp or nfs kernel netboot should be
+ * considered.
+ */
+ b->ip.hdr.tcp_win = htons(PKTBUFSRX * TCP_MSS >> TCP_SCALE);
+
+ b->ip.hdr.tcp_xsum = 0;
+ b->ip.hdr.tcp_ugr = 0;
+
+ b->ip.hdr.tcp_xsum = tcp_set_pseudo_header(pkt, net_ip, net_server_ip,
+ tcp_len, pkt_len);
+
+ net_set_ip_header((uchar *)&b->ip, net_server_ip, net_ip,
+ pkt_len, IPPROTO_TCP);
+
+ return pkt_hdr_len;
+}
+
+/**
+ * tcp_hole() - Selective Acknowledgment (Essential for fast stream transfer)
+ * @tcp_seq_num: TCP sequence start number
+ * @len: the length of sequence numbers
+ */
+void tcp_hole(u32 tcp_seq_num, u32 len)
+{
+ u32 idx_sack, sack_in;
+ u32 sack_end = TCP_SACK - 1;
+ u32 hill = 0;
+ enum pkt_state expect = PKT;
+ u32 seq = tcp_seq_num - tcp_seq_init;
+ u32 hol_l = tcp_ack_edge - tcp_seq_init;
+ u32 hol_r = 0;
+
+ /* Place new seq number in correct place in receive array */
+ if (prev_len == 0)
+ prev_len = len;
+
+ idx_sack = sack_idx + ((tcp_seq_num - tcp_ack_edge) / prev_len);
+ if (idx_sack < TCP_SACK) {
+ edge_a[idx_sack].se.l = tcp_seq_num;
+ edge_a[idx_sack].se.r = tcp_seq_num + len;
+ edge_a[idx_sack].st = PKT;
+
+ /*
+ * The fin (last) packet is not the same length as data
+ * packets, and if it's length is recorded and used for
+ * array index calculation, calculation breaks.
+ */
+ if (prev_len < len)
+ prev_len = len;
+ }
+
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP 1 seq %d, edg %d, len %d, sack_idx %d, sack_end %d\n",
+ seq, hol_l, len, sack_idx, sack_end);
+
+ /* Right edge of contiguous stream, is the left edge of first hill */
+ hol_l = tcp_seq_num - tcp_seq_init;
+ hol_r = hol_l + len;
+
+ if (IS_ENABLED(CONFIG_PROT_TCP_SACK))
+ tcp_lost.len = TCP_OPT_LEN_2;
+
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP 1 in %d, seq %d, pkt_l %d, pkt_r %d, sack_idx %d, sack_end %d\n",
+ idx_sack, seq, hol_l, hol_r, sack_idx, sack_end);
+
+ for (sack_in = sack_idx; sack_in < sack_end && hill < TCP_SACK_HILLS;
+ sack_in++) {
+ switch (expect) {
+ case NOPKT:
+ switch (edge_a[sack_in].st) {
+ case NOPKT:
+ debug_cond(DEBUG_INT_STATE, "N");
+ break;
+ case PKT:
+ debug_cond(DEBUG_INT_STATE, "n");
+ if (IS_ENABLED(CONFIG_PROT_TCP_SACK)) {
+ tcp_lost.hill[hill].l =
+ edge_a[sack_in].se.l;
+ tcp_lost.hill[hill].r =
+ edge_a[sack_in].se.r;
+ }
+ expect = PKT;
+ break;
+ }
+ break;
+ case PKT:
+ switch (edge_a[sack_in].st) {
+ case NOPKT:
+ debug_cond(DEBUG_INT_STATE, "p");
+ if (sack_in > sack_idx &&
+ hill < TCP_SACK_HILLS) {
+ hill++;
+ if (IS_ENABLED(CONFIG_PROT_TCP_SACK))
+ tcp_lost.len += TCP_OPT_LEN_8;
+ }
+ expect = NOPKT;
+ break;
+ case PKT:
+ debug_cond(DEBUG_INT_STATE, "P");
+
+ if (tcp_ack_edge == edge_a[sack_in].se.l) {
+ tcp_ack_edge = edge_a[sack_in].se.r;
+ edge_a[sack_in].st = NOPKT;
+ sack_idx++;
+ } else {
+ if (IS_ENABLED(CONFIG_PROT_TCP_SACK) &&
+ hill < TCP_SACK_HILLS)
+ tcp_lost.hill[hill].r =
+ edge_a[sack_in].se.r;
+ if (IS_ENABLED(CONFIG_PROT_TCP_SACK) &&
+ sack_in == sack_end - 1)
+ tcp_lost.hill[hill].r =
+ edge_a[sack_in].se.r;
+ }
+ break;
+ }
+ break;
+ }
+ }
+ debug_cond(DEBUG_INT_STATE, "\n");
+ if (!IS_ENABLED(CONFIG_PROT_TCP_SACK) || tcp_lost.len <= TCP_OPT_LEN_2)
+ sack_idx = 0;
+}
+
+/**
+ * tcp_parse_options() - parsing TCP options
+ * @o: pointer to the option field.
+ * @o_len: length of the option field.
+ */
+void tcp_parse_options(uchar *o, int o_len)
+{
+ struct tcp_t_opt *tsopt;
+ uchar *p = o;
+
+ /*
+ * NOPs are options with a zero length, and thus are special.
+ * All other options have length fields.
+ */
+ for (p = o; p < (o + o_len); p = p + p[1]) {
+ if (!p[1])
+ return; /* Finished processing options */
+
+ switch (p[0]) {
+ case TCP_O_END:
+ return;
+ case TCP_O_MSS:
+ case TCP_O_SCL:
+ case TCP_P_SACK:
+ case TCP_V_SACK:
+ break;
+ case TCP_O_TS:
+ tsopt = (struct tcp_t_opt *)p;
+ rmt_timestamp = tsopt->t_snd;
+ return;
+ }
+
+ /* Process optional NOPs */
+ if (p[0] == TCP_O_NOP)
+ p++;
+ }
+}
+
+static u8 tcp_state_machine(u8 tcp_flags, u32 tcp_seq_num, int payload_len)
+{
+ u8 tcp_fin = tcp_flags & TCP_FIN;
+ u8 tcp_syn = tcp_flags & TCP_SYN;
+ u8 tcp_rst = tcp_flags & TCP_RST;
+ u8 tcp_push = tcp_flags & TCP_PUSH;
+ u8 tcp_ack = tcp_flags & TCP_ACK;
+ u8 action = TCP_DATA;
+ int i;
+
+ /*
+ * tcp_flags are examined to determine TX action in a given state
+ * tcp_push is interpreted to mean "inform the app"
+ * urg, ece, cer and nonce flags are not supported.
+ *
+ * exe and crw are use to signal and confirm knowledge of congestion.
+ * This TCP only sends a file request and acks. If it generates
+ * congestion, the network is broken.
+ */
+ debug_cond(DEBUG_INT_STATE, "TCP STATE ENTRY %x\n", action);
+ if (tcp_rst) {
+ action = TCP_DATA;
+ current_tcp_state = TCP_CLOSED;
+ net_set_state(NETLOOP_FAIL);
+ debug_cond(DEBUG_INT_STATE, "TCP Reset %x\n", tcp_flags);
+ return TCP_RST;
+ }
+
+ switch (current_tcp_state) {
+ case TCP_CLOSED:
+ debug_cond(DEBUG_INT_STATE, "TCP CLOSED %x\n", tcp_flags);
+ if (tcp_syn) {
+ action = TCP_SYN | TCP_ACK;
+ tcp_seq_init = tcp_seq_num;
+ tcp_ack_edge = tcp_seq_num + 1;
+ current_tcp_state = TCP_SYN_RECEIVED;
+ } else if (tcp_ack || tcp_fin) {
+ action = TCP_DATA;
+ }
+ break;
+ case TCP_SYN_RECEIVED:
+ case TCP_SYN_SENT:
+ debug_cond(DEBUG_INT_STATE, "TCP_SYN_SENT | TCP_SYN_RECEIVED %x, %u\n",
+ tcp_flags, tcp_seq_num);
+ if (tcp_fin) {
+ action = action | TCP_PUSH;
+ current_tcp_state = TCP_CLOSE_WAIT;
+ } else if (tcp_ack || (tcp_syn && tcp_ack)) {
+ action |= TCP_ACK;
+ tcp_seq_init = tcp_seq_num;
+ tcp_ack_edge = tcp_seq_num + 1;
+ sack_idx = 0;
+ edge_a[sack_idx].se.l = tcp_ack_edge;
+ edge_a[sack_idx].se.r = tcp_ack_edge;
+ prev_len = 0;
+ current_tcp_state = TCP_ESTABLISHED;
+ for (i = 0; i < TCP_SACK; i++)
+ edge_a[i].st = NOPKT;
+
+ if (tcp_syn && tcp_ack)
+ action |= TCP_PUSH;
+ } else {
+ action = TCP_DATA;
+ }
+ break;
+ case TCP_ESTABLISHED:
+ debug_cond(DEBUG_INT_STATE, "TCP_ESTABLISHED %x\n", tcp_flags);
+ if (payload_len > 0) {
+ tcp_hole(tcp_seq_num, payload_len);
+ tcp_fin = TCP_DATA; /* cause standalone FIN */
+ }
+
+ if ((tcp_fin) &&
+ (!IS_ENABLED(CONFIG_PROT_TCP_SACK) ||
+ tcp_lost.len <= TCP_OPT_LEN_2)) {
+ action = action | TCP_FIN | TCP_PUSH | TCP_ACK;
+ current_tcp_state = TCP_CLOSE_WAIT;
+ } else if (tcp_ack) {
+ action = TCP_DATA;
+ }
+
+ if (tcp_syn)
+ action = TCP_ACK + TCP_RST;
+ else if (tcp_push)
+ action = action | TCP_PUSH;
+ break;
+ case TCP_CLOSE_WAIT:
+ debug_cond(DEBUG_INT_STATE, "TCP_CLOSE_WAIT (%x)\n", tcp_flags);
+ action = TCP_DATA;
+ break;
+ case TCP_FIN_WAIT_2:
+ debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_2 (%x)\n", tcp_flags);
+ if (tcp_ack) {
+ action = TCP_PUSH | TCP_ACK;
+ current_tcp_state = TCP_CLOSED;
+ puts("\n");
+ } else if (tcp_syn) {
+ action = TCP_DATA;
+ } else if (tcp_fin) {
+ action = TCP_DATA;
+ }
+ break;
+ case TCP_FIN_WAIT_1:
+ debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_1 (%x)\n", tcp_flags);
+ if (tcp_fin) {
+ tcp_ack_edge++;
+ action = TCP_ACK | TCP_FIN;
+ current_tcp_state = TCP_FIN_WAIT_2;
+ }
+ if (tcp_syn)
+ action = TCP_RST;
+ if (tcp_ack)
+ current_tcp_state = TCP_CLOSED;
+ break;
+ case TCP_CLOSING:
+ debug_cond(DEBUG_INT_STATE, "TCP_CLOSING (%x)\n", tcp_flags);
+ if (tcp_ack) {
+ action = TCP_PUSH;
+ current_tcp_state = TCP_CLOSED;
+ puts("\n");
+ } else if (tcp_syn) {
+ action = TCP_RST;
+ } else if (tcp_fin) {
+ action = TCP_DATA;
+ }
+ break;
+ }
+ return action;
+}
+
+/**
+ * rxhand_tcp_f() - process receiving data and call data handler.
+ * @b: the packet
+ * @pkt_len: the length of packet.
+ */
+void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int pkt_len)
+{
+ int tcp_len = pkt_len - IP_HDR_SIZE;
+ u16 tcp_rx_xsum = b->ip.hdr.ip_sum;
+ u8 tcp_action = TCP_DATA;
+ u32 tcp_seq_num, tcp_ack_num;
+ int tcp_hdr_len, payload_len;
+
+ /* Verify IP header */
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP RX in RX Sum (to=%pI4, from=%pI4, len=%d)\n",
+ &b->ip.hdr.ip_src, &b->ip.hdr.ip_dst, pkt_len);
+
+ b->ip.hdr.ip_src = net_server_ip;
+ b->ip.hdr.ip_dst = net_ip;
+ b->ip.hdr.ip_sum = 0;
+ if (tcp_rx_xsum != compute_ip_checksum(b, IP_HDR_SIZE)) {
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP RX IP xSum Error (%pI4, =%pI4, len=%d)\n",
+ &net_ip, &net_server_ip, pkt_len);
+ return;
+ }
+
+ /* Build pseudo header and verify TCP header */
+ tcp_rx_xsum = b->ip.hdr.tcp_xsum;
+ b->ip.hdr.tcp_xsum = 0;
+ if (tcp_rx_xsum != tcp_set_pseudo_header((uchar *)b, b->ip.hdr.ip_src,
+ b->ip.hdr.ip_dst, tcp_len,
+ pkt_len)) {
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP RX TCP xSum Error (%pI4, %pI4, len=%d)\n",
+ &net_ip, &net_server_ip, tcp_len);
+ return;
+ }
+
+ tcp_hdr_len = GET_TCP_HDR_LEN_IN_BYTES(b->ip.hdr.tcp_hlen);
+ payload_len = tcp_len - tcp_hdr_len;
+
+ if (tcp_hdr_len > TCP_HDR_SIZE)
+ tcp_parse_options((uchar *)b + IP_TCP_HDR_SIZE,
+ tcp_hdr_len - TCP_HDR_SIZE);
+ /*
+ * Incoming sequence and ack numbers are server's view of the numbers.
+ * The app must swap the numbers when responding.
+ */
+ tcp_seq_num = ntohl(b->ip.hdr.tcp_seq);
+ tcp_ack_num = ntohl(b->ip.hdr.tcp_ack);
+
+ /* Packets are not ordered. Send to app as received. */
+ tcp_action = tcp_state_machine(b->ip.hdr.tcp_flags,
+ tcp_seq_num, payload_len);
+
+ tcp_activity_count++;
+ if (tcp_activity_count > TCP_ACTIVITY) {
+ puts("| ");
+ tcp_activity_count = 0;
+ }
+
+ if ((tcp_action & TCP_PUSH) || payload_len > 0) {
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP Notify (action=%x, Seq=%u,Ack=%u,Pay%d)\n",
+ tcp_action, tcp_seq_num, tcp_ack_num, payload_len);
+
+ (*tcp_packet_handler) ((uchar *)b + pkt_len - payload_len, b->ip.hdr.tcp_dst,
+ b->ip.hdr.ip_src, b->ip.hdr.tcp_src, tcp_seq_num,
+ tcp_ack_num, tcp_action, payload_len);
+
+ } else if (tcp_action != TCP_DATA) {
+ debug_cond(DEBUG_DEV_PKT,
+ "TCP Action (action=%x,Seq=%u,Ack=%u,Pay=%d)\n",
+ tcp_action, tcp_ack_num, tcp_ack_edge, payload_len);
+
+ /*
+ * Warning: Incoming Ack & Seq sequence numbers are transposed
+ * here to outgoing Seq & Ack sequence numbers
+ */
+ net_send_tcp_packet(0, ntohs(b->ip.hdr.tcp_src),
+ ntohs(b->ip.hdr.tcp_dst),
+ (tcp_action & (~TCP_PUSH)),
+ tcp_ack_num, tcp_ack_edge);
+ }
+}
diff --git a/net/tftp.c b/net/tftp.c
new file mode 100644
index 00000000000..704b20b4ff8
--- /dev/null
+++ b/net/tftp.c
@@ -0,0 +1,989 @@
+/*
+ * Copyright 1994, 1995, 2000 Neil Russell.
+ * (See License)
+ * Copyright 2000, 2001 DENX Software Engineering, Wolfgang Denk, wd@denx.de
+ * Copyright 2011 Comelit Group SpA,
+ * Luca Ceresoli <luca.ceresoli@comelit.it>
+ */
+#include <command.h>
+#include <display_options.h>
+#include <efi_loader.h>
+#include <env.h>
+#include <image.h>
+#include <led.h>
+#include <lmb.h>
+#include <log.h>
+#include <mapmem.h>
+#include <net.h>
+#include <net6.h>
+#include <asm/global_data.h>
+#include <net/tftp.h>
+#include "bootp.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* Well known TFTP port # */
+#define WELL_KNOWN_PORT 69
+/* Millisecs to timeout for lost pkt */
+#define TIMEOUT 5000UL
+/* Number of "loading" hashes per line (for checking the image size) */
+#define HASHES_PER_LINE 65
+
+/*
+ * TFTP operations.
+ */
+#define TFTP_RRQ 1
+#define TFTP_WRQ 2
+#define TFTP_DATA 3
+#define TFTP_ACK 4
+#define TFTP_ERROR 5
+#define TFTP_OACK 6
+
+static ulong timeout_ms = TIMEOUT;
+static int timeout_count_max = (CONFIG_NET_RETRY_COUNT * 2);
+static ulong time_start; /* Record time we started tftp */
+static struct in6_addr tftp_remote_ip6;
+
+/*
+ * These globals govern the timeout behavior when attempting a connection to a
+ * TFTP server. tftp_timeout_ms specifies the number of milliseconds to
+ * wait for the server to respond to initial connection. Second global,
+ * tftp_timeout_count_max, gives the number of such connection retries.
+ * tftp_timeout_count_max must be non-negative and tftp_timeout_ms must be
+ * positive. The globals are meant to be set (and restored) by code needing
+ * non-standard timeout behavior when initiating a TFTP transfer.
+ */
+ulong tftp_timeout_ms = TIMEOUT;
+int tftp_timeout_count_max = (CONFIG_NET_RETRY_COUNT * 2);
+
+enum {
+ TFTP_ERR_UNDEFINED = 0,
+ TFTP_ERR_FILE_NOT_FOUND = 1,
+ TFTP_ERR_ACCESS_DENIED = 2,
+ TFTP_ERR_DISK_FULL = 3,
+ TFTP_ERR_UNEXPECTED_OPCODE = 4,
+ TFTP_ERR_UNKNOWN_TRANSFER_ID = 5,
+ TFTP_ERR_FILE_ALREADY_EXISTS = 6,
+ TFTP_ERR_OPTION_NEGOTIATION = 8,
+};
+
+static struct in_addr tftp_remote_ip;
+/* The UDP port at their end */
+static int tftp_remote_port;
+/* The UDP port at our end */
+static int tftp_our_port;
+static int timeout_count;
+/* packet sequence number */
+static ulong tftp_cur_block;
+/* last packet sequence number received */
+static ulong tftp_prev_block;
+/* count of sequence number wraparounds */
+static ulong tftp_block_wrap;
+/* memory offset due to wrapping */
+static ulong tftp_block_wrap_offset;
+static int tftp_state;
+static ulong tftp_load_addr;
+#ifdef CONFIG_TFTP_TSIZE
+/* The file size reported by the server */
+static int tftp_tsize;
+/* The number of hashes we printed */
+static short tftp_tsize_num_hash;
+#endif
+/* The window size negotiated */
+static ushort tftp_windowsize;
+/* Next block to send ack to */
+static ushort tftp_next_ack;
+/* Last nack block we send */
+static ushort tftp_last_nack;
+#ifdef CONFIG_CMD_TFTPPUT
+/* 1 if writing, else 0 */
+static int tftp_put_active;
+/* 1 if we have sent the last block */
+static int tftp_put_final_block_sent;
+#else
+#define tftp_put_active 0
+#endif
+
+#define STATE_SEND_RRQ 1
+#define STATE_DATA 2
+#define STATE_TOO_LARGE 3
+#define STATE_BAD_MAGIC 4
+#define STATE_OACK 5
+#define STATE_RECV_WRQ 6
+#define STATE_SEND_WRQ 7
+#define STATE_INVALID_OPTION 8
+
+/* default TFTP block size */
+#define TFTP_BLOCK_SIZE 512
+#define TFTP_MTU_BLOCKSIZE6 (CONFIG_TFTP_BLOCKSIZE - 20)
+/* sequence number is 16 bit */
+#define TFTP_SEQUENCE_SIZE ((ulong)(1<<16))
+
+#define DEFAULT_NAME_LEN (8 + 4 + 1)
+static char default_filename[DEFAULT_NAME_LEN];
+
+#ifndef CONFIG_TFTP_FILE_NAME_MAX_LEN
+#define MAX_LEN 128
+#else
+#define MAX_LEN CONFIG_TFTP_FILE_NAME_MAX_LEN
+#endif
+
+static char tftp_filename[MAX_LEN];
+
+/* 512 is poor choice for ethernet, MTU is typically 1500.
+ * Minus eth.hdrs thats 1468. Can get 2x better throughput with
+ * almost-MTU block sizes. At least try... fall back to 512 if need be.
+ * (but those using CONFIG_IP_DEFRAG may want to set a larger block in cfg file)
+ */
+
+/* When windowsize is defined to 1,
+ * tftp behaves the same way as it was
+ * never declared
+ */
+#ifdef CONFIG_TFTP_WINDOWSIZE
+#define TFTP_WINDOWSIZE CONFIG_TFTP_WINDOWSIZE
+#else
+#define TFTP_WINDOWSIZE 1
+#endif
+
+static unsigned short tftp_block_size = TFTP_BLOCK_SIZE;
+static unsigned short tftp_block_size_option = CONFIG_TFTP_BLOCKSIZE;
+static unsigned short tftp_window_size_option = TFTP_WINDOWSIZE;
+
+static inline int store_block(int block, uchar *src, unsigned int len)
+{
+ ulong offset = block * tftp_block_size + tftp_block_wrap_offset -
+ tftp_block_size;
+ ulong newsize = offset + len;
+ ulong store_addr = tftp_load_addr + offset;
+ void *ptr;
+
+ if (CONFIG_IS_ENABLED(LMB)) {
+ if (store_addr < tftp_load_addr ||
+ lmb_read_check(store_addr, len)) {
+ puts("\nTFTP error: ");
+ puts("trying to overwrite reserved memory...\n");
+ return -1;
+ }
+ }
+
+ ptr = map_sysmem(store_addr, len);
+ memcpy(ptr, src, len);
+ unmap_sysmem(ptr);
+
+ if (net_boot_file_size < newsize)
+ net_boot_file_size = newsize;
+
+ return 0;
+}
+
+/* Clear our state ready for a new transfer */
+static void new_transfer(void)
+{
+ tftp_prev_block = 0;
+ tftp_block_wrap = 0;
+ tftp_block_wrap_offset = 0;
+#ifdef CONFIG_CMD_TFTPPUT
+ tftp_put_final_block_sent = 0;
+#endif
+ led_activity_blink();
+}
+
+#ifdef CONFIG_CMD_TFTPPUT
+/**
+ * Load the next block from memory to be sent over tftp.
+ *
+ * @param block Block number to send
+ * @param dst Destination buffer for data
+ * @param len Number of bytes in block (this one and every other)
+ * Return: number of bytes loaded
+ */
+static int load_block(unsigned block, uchar *dst, unsigned len)
+{
+ /* We may want to get the final block from the previous set */
+ ulong offset = block * tftp_block_size + tftp_block_wrap_offset -
+ tftp_block_size;
+ ulong tosend = len;
+
+ tosend = min(net_boot_file_size - offset, tosend);
+ (void)memcpy(dst, (void *)(image_save_addr + offset), tosend);
+ debug("%s: block=%u, offset=%lu, len=%u, tosend=%lu\n", __func__,
+ block, offset, len, tosend);
+ return tosend;
+}
+#endif
+
+static void tftp_send(void);
+static void tftp_timeout_handler(void);
+
+/**********************************************************************/
+
+static void show_block_marker(void)
+{
+ ulong pos;
+
+#ifdef CONFIG_TFTP_TSIZE
+ if (tftp_tsize) {
+ pos = tftp_cur_block * tftp_block_size +
+ tftp_block_wrap_offset;
+ if (pos > tftp_tsize)
+ pos = tftp_tsize;
+
+ while (tftp_tsize_num_hash < pos * 50 / tftp_tsize) {
+ putc('#');
+ tftp_tsize_num_hash++;
+ }
+ } else
+#endif
+ {
+ pos = (tftp_cur_block - 1) +
+ (tftp_block_wrap * TFTP_SEQUENCE_SIZE);
+ if ((pos % 10) == 0)
+ putc('#');
+ else if (((pos + 1) % (10 * HASHES_PER_LINE)) == 0)
+ puts("\n\t ");
+ }
+}
+
+/**
+ * restart the current transfer due to an error
+ *
+ * @param msg Message to print for user
+ */
+static void restart(const char *msg)
+{
+ printf("\n%s; starting again\n", msg);
+ net_start_again();
+}
+
+/*
+ * Check if the block number has wrapped, and update progress
+ *
+ * TODO: The egregious use of global variables in this file should be tidied.
+ */
+static void update_block_number(void)
+{
+ /*
+ * RFC1350 specifies that the first data packet will
+ * have sequence number 1. If we receive a sequence
+ * number of 0 this means that there was a wrap
+ * around of the (16 bit) counter.
+ */
+ if (tftp_cur_block == 0 && tftp_prev_block != 0) {
+ tftp_block_wrap++;
+ tftp_block_wrap_offset += tftp_block_size * TFTP_SEQUENCE_SIZE;
+ timeout_count = 0; /* we've done well, reset the timeout */
+ }
+ show_block_marker();
+}
+
+/* The TFTP get or put is complete */
+static void tftp_complete(void)
+{
+#ifdef CONFIG_TFTP_TSIZE
+ /* Print hash marks for the last packet received */
+ while (tftp_tsize && tftp_tsize_num_hash < 49) {
+ putc('#');
+ tftp_tsize_num_hash++;
+ }
+ puts(" ");
+ print_size(tftp_tsize, "");
+#endif
+ time_start = get_timer(time_start);
+ if (time_start > 0) {
+ puts("\n\t "); /* Line up with "Loading: " */
+ print_size(net_boot_file_size /
+ time_start * 1000, "/s");
+ }
+ puts("\ndone\n");
+
+ led_activity_off();
+
+ if (!tftp_put_active)
+ efi_set_bootdev("Net", "", tftp_filename,
+ map_sysmem(tftp_load_addr, 0),
+ net_boot_file_size);
+ net_set_state(NETLOOP_SUCCESS);
+}
+
+static void tftp_send(void)
+{
+ uchar *pkt;
+ uchar *xp;
+ int len = 0;
+ ushort *s;
+ bool err_pkt = false;
+
+ /*
+ * We will always be sending some sort of packet, so
+ * cobble together the packet headers now.
+ */
+ if (IS_ENABLED(CONFIG_IPV6) && use_ip6)
+ pkt = net_tx_packet + net_eth_hdr_size() +
+ IP6_HDR_SIZE + UDP_HDR_SIZE;
+ else
+ pkt = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE;
+
+ switch (tftp_state) {
+ case STATE_SEND_RRQ:
+ case STATE_SEND_WRQ:
+ xp = pkt;
+ s = (ushort *)pkt;
+#ifdef CONFIG_CMD_TFTPPUT
+ *s++ = htons(tftp_state == STATE_SEND_RRQ ? TFTP_RRQ :
+ TFTP_WRQ);
+#else
+ *s++ = htons(TFTP_RRQ);
+#endif
+ pkt = (uchar *)s;
+ strcpy((char *)pkt, tftp_filename);
+ pkt += strlen(tftp_filename) + 1;
+ strcpy((char *)pkt, "octet");
+ pkt += 5 /*strlen("octet")*/ + 1;
+ strcpy((char *)pkt, "timeout");
+ pkt += 7 /*strlen("timeout")*/ + 1;
+ sprintf((char *)pkt, "%lu", timeout_ms / 1000);
+ debug("send option \"timeout %s\"\n", (char *)pkt);
+ pkt += strlen((char *)pkt) + 1;
+#ifdef CONFIG_TFTP_TSIZE
+ pkt += sprintf((char *)pkt, "tsize%c%u%c",
+ 0, net_boot_file_size, 0);
+#endif
+ /* try for more effic. blk size */
+ pkt += sprintf((char *)pkt, "blksize%c%d%c",
+ 0, tftp_block_size_option, 0);
+
+ /* try for more effic. window size.
+ * Implemented only for tftp get.
+ * Don't bother sending if it's 1
+ */
+ if (tftp_state == STATE_SEND_RRQ && tftp_window_size_option > 1)
+ pkt += sprintf((char *)pkt, "windowsize%c%d%c",
+ 0, tftp_window_size_option, 0);
+ len = pkt - xp;
+ break;
+
+ case STATE_OACK:
+
+ case STATE_RECV_WRQ:
+ case STATE_DATA:
+ xp = pkt;
+ s = (ushort *)pkt;
+ s[0] = htons(TFTP_ACK);
+ s[1] = htons(tftp_cur_block);
+ pkt = (uchar *)(s + 2);
+#ifdef CONFIG_CMD_TFTPPUT
+ if (tftp_put_active) {
+ int toload = tftp_block_size;
+ int loaded = load_block(tftp_cur_block, pkt, toload);
+
+ s[0] = htons(TFTP_DATA);
+ pkt += loaded;
+ tftp_put_final_block_sent = (loaded < toload);
+ }
+#endif
+ len = pkt - xp;
+ break;
+
+ case STATE_TOO_LARGE:
+ xp = pkt;
+ s = (ushort *)pkt;
+ *s++ = htons(TFTP_ERROR);
+ *s++ = htons(3);
+
+ pkt = (uchar *)s;
+ strcpy((char *)pkt, "File too large");
+ pkt += 14 /*strlen("File too large")*/ + 1;
+ len = pkt - xp;
+ err_pkt = true;
+ break;
+
+ case STATE_BAD_MAGIC:
+ xp = pkt;
+ s = (ushort *)pkt;
+ *s++ = htons(TFTP_ERROR);
+ *s++ = htons(2);
+ pkt = (uchar *)s;
+ strcpy((char *)pkt, "File has bad magic");
+ pkt += 18 /*strlen("File has bad magic")*/ + 1;
+ len = pkt - xp;
+ err_pkt = true;
+ break;
+
+ case STATE_INVALID_OPTION:
+ xp = pkt;
+ s = (ushort *)pkt;
+ *s++ = htons(TFTP_ERROR);
+ *s++ = htons(TFTP_ERR_OPTION_NEGOTIATION);
+ pkt = (uchar *)s;
+ strcpy((char *)pkt, "Option Negotiation Failed");
+ /* strlen("Option Negotiation Failed") + NULL*/
+ pkt += 25 + 1;
+ len = pkt - xp;
+ err_pkt = true;
+ break;
+ }
+
+ if (IS_ENABLED(CONFIG_IPV6) && use_ip6)
+ net_send_udp_packet6(net_server_ethaddr,
+ &tftp_remote_ip6,
+ tftp_remote_port,
+ tftp_our_port, len);
+ else
+ net_send_udp_packet(net_server_ethaddr, tftp_remote_ip,
+ tftp_remote_port, tftp_our_port, len);
+
+ if (err_pkt)
+ net_set_state(NETLOOP_FAIL);
+}
+
+#ifdef CONFIG_CMD_TFTPPUT
+static void icmp_handler(unsigned type, unsigned code, unsigned dest,
+ struct in_addr sip, unsigned src, uchar *pkt,
+ unsigned len)
+{
+ if (type == ICMP_NOT_REACH && code == ICMP_NOT_REACH_PORT) {
+ /* Oh dear the other end has gone away */
+ restart("TFTP server died");
+ }
+}
+#endif
+
+static void tftp_handler(uchar *pkt, unsigned dest, struct in_addr sip,
+ unsigned src, unsigned len)
+{
+ __be16 proto;
+ __be16 *s;
+ int i;
+ u16 timeout_val_rcvd;
+
+ if (dest != tftp_our_port) {
+ return;
+ }
+ if (tftp_state != STATE_SEND_RRQ && src != tftp_remote_port &&
+ tftp_state != STATE_RECV_WRQ && tftp_state != STATE_SEND_WRQ)
+ return;
+
+ if (len < 2)
+ return;
+ len -= 2;
+ /* warning: don't use increment (++) in ntohs() macros!! */
+ s = (__be16 *)pkt;
+ proto = *s++;
+ pkt = (uchar *)s;
+ switch (ntohs(proto)) {
+ case TFTP_RRQ:
+ break;
+
+ case TFTP_ACK:
+#ifdef CONFIG_CMD_TFTPPUT
+ if (tftp_put_active) {
+ if (tftp_put_final_block_sent) {
+ tftp_complete();
+ } else {
+ /*
+ * Move to the next block. We want our block
+ * count to wrap just like the other end!
+ */
+ int block = ntohs(*s);
+ int ack_ok = (tftp_cur_block == block);
+
+ tftp_prev_block = tftp_cur_block;
+ tftp_cur_block = (unsigned short)(block + 1);
+ update_block_number();
+ if (ack_ok) {
+ if (block == 0 &&
+ tftp_state == STATE_SEND_WRQ){
+ /* connection's first ACK */
+ tftp_state = STATE_DATA;
+ tftp_remote_port = src;
+ }
+ tftp_send(); /* Send next data block */
+ }
+ }
+ }
+#endif
+ break;
+
+ default:
+ break;
+
+#ifdef CONFIG_CMD_TFTPSRV
+ case TFTP_WRQ:
+ debug("Got WRQ\n");
+ tftp_remote_ip = sip;
+ tftp_remote_port = src;
+ tftp_our_port = 1024 + (get_timer(0) % 3072);
+ new_transfer();
+ tftp_send(); /* Send ACK(0) */
+ break;
+#endif
+
+ case TFTP_OACK:
+ debug("Got OACK: ");
+ for (i = 0; i < len; i++) {
+ if (pkt[i] == '\0')
+ debug(" ");
+ else
+ debug("%c", pkt[i]);
+ }
+ debug("\n");
+ tftp_state = STATE_OACK;
+ tftp_remote_port = src;
+ /*
+ * Check for 'blksize' option.
+ * Careful: "i" is signed, "len" is unsigned, thus
+ * something like "len-8" may give a *huge* number
+ */
+ for (i = 0; i+8 < len; i++) {
+ if (strcasecmp((char *)pkt + i, "blksize") == 0) {
+ tftp_block_size = (unsigned short)
+ dectoul((char *)pkt + i + 8, NULL);
+ debug("Blocksize oack: %s, %d\n",
+ (char *)pkt + i + 8, tftp_block_size);
+ if (tftp_block_size > tftp_block_size_option) {
+ printf("Invalid blk size(=%d)\n",
+ tftp_block_size);
+ tftp_state = STATE_INVALID_OPTION;
+ }
+ }
+ if (strcasecmp((char *)pkt + i, "timeout") == 0) {
+ timeout_val_rcvd = (unsigned short)
+ dectoul((char *)pkt + i + 8, NULL);
+ debug("Timeout oack: %s, %d\n",
+ (char *)pkt + i + 8, timeout_val_rcvd);
+ if (timeout_val_rcvd != (timeout_ms / 1000)) {
+ printf("Invalid timeout val(=%d s)\n",
+ timeout_val_rcvd);
+ tftp_state = STATE_INVALID_OPTION;
+ }
+ }
+#ifdef CONFIG_TFTP_TSIZE
+ if (strcasecmp((char *)pkt + i, "tsize") == 0) {
+ tftp_tsize = dectoul((char *)pkt + i + 6,
+ NULL);
+ debug("size = %s, %d\n",
+ (char *)pkt + i + 6, tftp_tsize);
+ }
+#endif
+ if (strcasecmp((char *)pkt + i, "windowsize") == 0) {
+ tftp_windowsize =
+ dectoul((char *)pkt + i + 11, NULL);
+ debug("windowsize = %s, %d\n",
+ (char *)pkt + i + 11, tftp_windowsize);
+ }
+ }
+
+ tftp_next_ack = tftp_windowsize;
+
+#ifdef CONFIG_CMD_TFTPPUT
+ if (tftp_put_active && tftp_state == STATE_OACK) {
+ /* Get ready to send the first block */
+ tftp_state = STATE_DATA;
+ tftp_cur_block++;
+ }
+#endif
+ tftp_send(); /* Send ACK or first data block */
+ break;
+ case TFTP_DATA:
+ if (len < 2)
+ return;
+ len -= 2;
+
+ if (ntohs(*(__be16 *)pkt) != (ushort)(tftp_cur_block + 1)) {
+ debug("Received unexpected block: %d, expected: %d\n",
+ ntohs(*(__be16 *)pkt),
+ (ushort)(tftp_cur_block + 1));
+ /*
+ * Only ACK if the block count received is greater than
+ * the expected block count, otherwise skip ACK.
+ * (required to properly handle the server retransmitting
+ * the window)
+ */
+ if ((ushort)(tftp_cur_block + 1) - (short)(ntohs(*(__be16 *)pkt)) > 0)
+ break;
+ /*
+ * If one packet is dropped most likely
+ * all other buffers in the window
+ * that will arrive will cause a sending NACK.
+ * This just overwellms the server, let's just send one.
+ */
+ if (tftp_last_nack != tftp_cur_block) {
+ tftp_send();
+ tftp_last_nack = tftp_cur_block;
+ tftp_next_ack = (ushort)(tftp_cur_block +
+ tftp_windowsize);
+ }
+ break;
+ }
+
+ tftp_cur_block++;
+ tftp_cur_block %= TFTP_SEQUENCE_SIZE;
+
+ if (tftp_state == STATE_SEND_RRQ) {
+ debug("Server did not acknowledge any options!\n");
+ tftp_next_ack = tftp_windowsize;
+ }
+
+ if (tftp_state == STATE_SEND_RRQ || tftp_state == STATE_OACK ||
+ tftp_state == STATE_RECV_WRQ) {
+ /* first block received */
+ tftp_state = STATE_DATA;
+ tftp_remote_port = src;
+ new_transfer();
+
+ if (tftp_cur_block != 1) { /* Assertion */
+ puts("\nTFTP error: ");
+ printf("First block is not block 1 (%ld)\n",
+ tftp_cur_block);
+ puts("Starting again\n\n");
+ net_start_again();
+ break;
+ }
+ }
+
+ if (tftp_cur_block == tftp_prev_block) {
+ /* Same block again; ignore it. */
+ break;
+ }
+
+ update_block_number();
+ tftp_prev_block = tftp_cur_block;
+ timeout_count_max = tftp_timeout_count_max;
+ net_set_timeout_handler(timeout_ms, tftp_timeout_handler);
+
+ if (store_block(tftp_cur_block, pkt + 2, len)) {
+ eth_halt();
+ net_set_state(NETLOOP_FAIL);
+ break;
+ }
+
+ if (len < tftp_block_size) {
+ tftp_send();
+ tftp_complete();
+ break;
+ }
+
+ /*
+ * Acknowledge the block just received, which will prompt
+ * the remote for the next one.
+ */
+ if (tftp_cur_block == tftp_next_ack) {
+ tftp_send();
+ tftp_next_ack += tftp_windowsize;
+ }
+ break;
+
+ case TFTP_ERROR:
+ printf("\nTFTP error: '%s' (%d)\n",
+ pkt + 2, ntohs(*(__be16 *)pkt));
+
+ switch (ntohs(*(__be16 *)pkt)) {
+ case TFTP_ERR_FILE_NOT_FOUND:
+ case TFTP_ERR_ACCESS_DENIED:
+ puts("Not retrying...\n");
+ eth_halt();
+ net_set_state(NETLOOP_FAIL);
+ break;
+ case TFTP_ERR_UNDEFINED:
+ case TFTP_ERR_DISK_FULL:
+ case TFTP_ERR_UNEXPECTED_OPCODE:
+ case TFTP_ERR_UNKNOWN_TRANSFER_ID:
+ case TFTP_ERR_FILE_ALREADY_EXISTS:
+ default:
+ puts("Starting again\n\n");
+ net_start_again();
+ break;
+ }
+ break;
+ }
+}
+
+static void tftp_timeout_handler(void)
+{
+ if (++timeout_count > timeout_count_max) {
+ restart("Retry count exceeded");
+ } else {
+ puts("T ");
+ net_set_timeout_handler(timeout_ms, tftp_timeout_handler);
+ if (tftp_state != STATE_RECV_WRQ)
+ tftp_send();
+ }
+}
+
+static int tftp_init_load_addr(void)
+{
+ tftp_load_addr = image_load_addr;
+ return 0;
+}
+
+static int saved_tftp_block_size_option;
+static void sanitize_tftp_block_size_option(enum proto_t protocol)
+{
+ int cap, max_defrag;
+
+ switch (protocol) {
+ case TFTPGET:
+ max_defrag = config_opt_enabled(CONFIG_IP_DEFRAG, CONFIG_NET_MAXDEFRAG, 0);
+ if (max_defrag) {
+ /* Account for IP, UDP and TFTP headers. */
+ cap = max_defrag - (20 + 8 + 4);
+ /* RFC2348 sets a hard upper limit. */
+ cap = min(cap, 65464);
+ break;
+ }
+ /*
+ * If not CONFIG_IP_DEFRAG, cap at the same value as
+ * for tftp put, namely normal MTU minus protocol
+ * overhead.
+ */
+ fallthrough;
+ case TFTPPUT:
+ default:
+ /*
+ * U-Boot does not support IP fragmentation on TX, so
+ * this must be small enough that it fits normal MTU
+ * (and small enough that it fits net_tx_packet which
+ * has room for PKTSIZE_ALIGN bytes).
+ */
+ cap = 1468;
+ }
+ if (tftp_block_size_option > cap) {
+ printf("Capping tftp block size option to %d (was %d)\n",
+ cap, tftp_block_size_option);
+ saved_tftp_block_size_option = tftp_block_size_option;
+ tftp_block_size_option = cap;
+ }
+}
+
+void tftp_start(enum proto_t protocol)
+{
+ __maybe_unused char *ep; /* Environment pointer */
+
+ if (saved_tftp_block_size_option) {
+ tftp_block_size_option = saved_tftp_block_size_option;
+ saved_tftp_block_size_option = 0;
+ }
+
+ if (IS_ENABLED(CONFIG_NET_TFTP_VARS)) {
+
+ /*
+ * Allow the user to choose TFTP blocksize and timeout.
+ * TFTP protocol has a minimal timeout of 1 second.
+ */
+
+ ep = env_get("tftpblocksize");
+ if (ep != NULL)
+ tftp_block_size_option = simple_strtol(ep, NULL, 10);
+
+ ep = env_get("tftpwindowsize");
+ if (ep != NULL)
+ tftp_window_size_option = simple_strtol(ep, NULL, 10);
+
+ ep = env_get("tftptimeout");
+ if (ep != NULL)
+ timeout_ms = simple_strtol(ep, NULL, 10);
+
+ if (timeout_ms < 1000) {
+ printf("TFTP timeout (%ld ms) too low, set min = 1000 ms\n",
+ timeout_ms);
+ timeout_ms = 1000;
+ }
+
+ ep = env_get("tftptimeoutcountmax");
+ if (ep != NULL)
+ tftp_timeout_count_max = simple_strtol(ep, NULL, 10);
+
+ if (tftp_timeout_count_max < 0) {
+ printf("TFTP timeout count max (%d ms) negative, set to 0\n",
+ tftp_timeout_count_max);
+ tftp_timeout_count_max = 0;
+ }
+ }
+
+ sanitize_tftp_block_size_option(protocol);
+
+ debug("TFTP blocksize = %i, TFTP windowsize = %d timeout = %ld ms\n",
+ tftp_block_size_option, tftp_window_size_option, timeout_ms);
+
+ if (IS_ENABLED(CONFIG_IPV6))
+ tftp_remote_ip6 = net_server_ip6;
+
+ tftp_remote_ip = net_server_ip;
+ if (!net_parse_bootfile(&tftp_remote_ip, tftp_filename, MAX_LEN)) {
+ sprintf(default_filename, "%02X%02X%02X%02X.img",
+ net_ip.s_addr & 0xFF,
+ (net_ip.s_addr >> 8) & 0xFF,
+ (net_ip.s_addr >> 16) & 0xFF,
+ (net_ip.s_addr >> 24) & 0xFF);
+
+ strncpy(tftp_filename, default_filename, DEFAULT_NAME_LEN);
+ tftp_filename[DEFAULT_NAME_LEN - 1] = 0;
+
+ printf("*** Warning: no boot file name; using '%s'\n",
+ tftp_filename);
+ }
+
+ if (IS_ENABLED(CONFIG_IPV6)) {
+ if (use_ip6) {
+ char *s, *e;
+ size_t len;
+
+ s = strchr(net_boot_file_name, '[');
+ e = strchr(net_boot_file_name, ']');
+ len = e - s;
+ if (s && e) {
+ string_to_ip6(s + 1, len - 1, &tftp_remote_ip6);
+ strlcpy(tftp_filename, e + 2, MAX_LEN);
+ } else {
+ strlcpy(tftp_filename, net_boot_file_name, MAX_LEN);
+ tftp_filename[MAX_LEN - 1] = 0;
+ }
+ }
+ }
+
+ printf("Using %s device\n", eth_get_name());
+
+ if (IS_ENABLED(CONFIG_IPV6) && use_ip6) {
+ printf("TFTP from server %pI6c; our IP address is %pI6c",
+ &tftp_remote_ip6, &net_ip6);
+
+ if (tftp_block_size_option > TFTP_MTU_BLOCKSIZE6)
+ tftp_block_size_option = TFTP_MTU_BLOCKSIZE6;
+ } else {
+ printf("TFTP %s server %pI4; our IP address is %pI4",
+#ifdef CONFIG_CMD_TFTPPUT
+ protocol == TFTPPUT ? "to" : "from",
+#else
+ "from",
+#endif
+ &tftp_remote_ip, &net_ip);
+ }
+
+ /* Check if we need to send across this subnet */
+ if (IS_ENABLED(CONFIG_IPV6) && use_ip6) {
+ if (!ip6_addr_in_subnet(&net_ip6, &tftp_remote_ip6,
+ net_prefix_length))
+ printf("; sending through gateway %pI6c",
+ &net_gateway6);
+ } else if (net_gateway.s_addr && net_netmask.s_addr) {
+ struct in_addr our_net;
+ struct in_addr remote_net;
+
+ our_net.s_addr = net_ip.s_addr & net_netmask.s_addr;
+ remote_net.s_addr = tftp_remote_ip.s_addr & net_netmask.s_addr;
+ if (our_net.s_addr != remote_net.s_addr)
+ printf("; sending through gateway %pI4", &net_gateway);
+ }
+ putc('\n');
+
+ printf("Filename '%s'.", tftp_filename);
+
+ if (net_boot_file_expected_size_in_blocks) {
+ printf(" Size is 0x%x Bytes = ",
+ net_boot_file_expected_size_in_blocks << 9);
+ print_size(net_boot_file_expected_size_in_blocks << 9, "");
+ }
+
+ putc('\n');
+#ifdef CONFIG_CMD_TFTPPUT
+ tftp_put_active = (protocol == TFTPPUT);
+ if (tftp_put_active) {
+ printf("Save address: 0x%lx\n", image_save_addr);
+ printf("Save size: 0x%lx\n", image_save_size);
+ net_boot_file_size = image_save_size;
+ puts("Saving: *\b");
+ tftp_state = STATE_SEND_WRQ;
+ new_transfer();
+ } else
+#endif
+ {
+ if (tftp_init_load_addr()) {
+ eth_halt();
+ net_set_state(NETLOOP_FAIL);
+ puts("\nTFTP error: ");
+ puts("trying to overwrite reserved memory...\n");
+ return;
+ }
+ printf("Load address: 0x%lx\n", tftp_load_addr);
+ puts("Loading: *\b");
+ tftp_state = STATE_SEND_RRQ;
+ }
+
+ time_start = get_timer(0);
+ timeout_count_max = tftp_timeout_count_max;
+
+ net_set_timeout_handler(timeout_ms, tftp_timeout_handler);
+ net_set_udp_handler(tftp_handler);
+#ifdef CONFIG_CMD_TFTPPUT
+ net_set_icmp_handler(icmp_handler);
+#endif
+ tftp_remote_port = WELL_KNOWN_PORT;
+ timeout_count = 0;
+ /* Use a pseudo-random port unless a specific port is set */
+ tftp_our_port = 1024 + (get_timer(0) % 3072);
+
+#ifdef CONFIG_TFTP_PORT
+ ep = env_get("tftpdstp");
+ if (ep != NULL)
+ tftp_remote_port = simple_strtol(ep, NULL, 10);
+ ep = env_get("tftpsrcp");
+ if (ep != NULL)
+ tftp_our_port = simple_strtol(ep, NULL, 10);
+#endif
+ tftp_cur_block = 0;
+ tftp_windowsize = 1;
+ tftp_last_nack = 0;
+ /* zero out server ether in case the server ip has changed */
+ memset(net_server_ethaddr, 0, 6);
+ /* Revert tftp_block_size to dflt */
+ tftp_block_size = TFTP_BLOCK_SIZE;
+#ifdef CONFIG_TFTP_TSIZE
+ tftp_tsize = 0;
+ tftp_tsize_num_hash = 0;
+#endif
+
+ tftp_send();
+}
+
+#ifdef CONFIG_CMD_TFTPSRV
+void tftp_start_server(void)
+{
+ tftp_filename[0] = 0;
+
+ if (tftp_init_load_addr()) {
+ eth_halt();
+ net_set_state(NETLOOP_FAIL);
+ puts("\nTFTP error: trying to overwrite reserved memory...\n");
+ return;
+ }
+ printf("Using %s device\n", eth_get_name());
+ printf("Listening for TFTP transfer on %pI4\n", &net_ip);
+ printf("Load address: 0x%lx\n", tftp_load_addr);
+
+ puts("Loading: *\b");
+
+ timeout_count_max = tftp_timeout_count_max;
+ timeout_count = 0;
+ timeout_ms = TIMEOUT;
+ net_set_timeout_handler(timeout_ms, tftp_timeout_handler);
+
+ /* Revert tftp_block_size to dflt */
+ tftp_block_size = TFTP_BLOCK_SIZE;
+ tftp_cur_block = 0;
+ tftp_our_port = WELL_KNOWN_PORT;
+ tftp_windowsize = 1;
+ tftp_next_ack = tftp_windowsize;
+
+#ifdef CONFIG_TFTP_TSIZE
+ tftp_tsize = 0;
+ tftp_tsize_num_hash = 0;
+#endif
+
+ tftp_state = STATE_RECV_WRQ;
+ net_set_udp_handler(tftp_handler);
+
+ /* zero out server ether in case the server ip has changed */
+ memset(net_server_ethaddr, 0, 6);
+}
+#endif /* CONFIG_CMD_TFTPSRV */
diff --git a/net/udp.c b/net/udp.c
new file mode 100644
index 00000000000..37162260d17
--- /dev/null
+++ b/net/udp.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2020 Philippe Reynes <philippe.reynes@softathome.com>
+ */
+
+#include <net.h>
+#include <net/udp.h>
+
+static struct udp_ops *udp_ops;
+
+int udp_prereq(void)
+{
+ int ret = 0;
+
+ if (udp_ops->prereq)
+ ret = udp_ops->prereq(udp_ops->data);
+
+ return ret;
+}
+
+int udp_start(void)
+{
+ return udp_ops->start(udp_ops->data);
+}
+
+int udp_loop(struct udp_ops *ops)
+{
+ int ret = -1;
+
+ if (!ops) {
+ printf("%s: ops should not be null\n", __func__);
+ goto out;
+ }
+
+ if (!ops->start) {
+ printf("%s: no start function defined\n", __func__);
+ goto out;
+ }
+
+ udp_ops = ops;
+ ret = net_loop(UDP);
+
+ out:
+ return ret;
+}
diff --git a/net/wget.c b/net/wget.c
new file mode 100644
index 00000000000..635f82efbb3
--- /dev/null
+++ b/net/wget.c
@@ -0,0 +1,606 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * WGET/HTTP support driver based on U-BOOT's nfs.c
+ * Copyright Duncan Hare <dh@synoia.com> 2017
+ */
+
+#include <asm/global_data.h>
+#include <command.h>
+#include <display_options.h>
+#include <env.h>
+#include <efi_loader.h>
+#include <image.h>
+#include <lmb.h>
+#include <mapmem.h>
+#include <net.h>
+#include <net/tcp.h>
+#include <net/wget.h>
+#include <stdlib.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* The default, change with environment variable 'httpdstp' */
+#define SERVER_PORT 80
+
+static const char bootfile1[] = "GET ";
+static const char bootfile3[] = " HTTP/1.0\r\n\r\n";
+static const char http_eom[] = "\r\n\r\n";
+static const char http_ok[] = "200";
+static const char content_len[] = "Content-Length";
+static const char linefeed[] = "\r\n";
+static struct in_addr web_server_ip;
+static int our_port;
+static int wget_timeout_count;
+
+struct pkt_qd {
+ uchar *pkt;
+ unsigned int tcp_seq_num;
+ unsigned int len;
+};
+
+/*
+ * This is a control structure for out of order packets received.
+ * The actual packet bufers are in the kernel space, and are
+ * expected to be overwritten by the downloaded image.
+ */
+#define PKTQ_SZ (PKTBUFSRX / 4)
+static struct pkt_qd pkt_q[PKTQ_SZ];
+static int pkt_q_idx;
+static unsigned long content_length;
+static unsigned int packets;
+
+static unsigned int initial_data_seq_num;
+static unsigned int next_data_seq_num;
+
+static enum wget_state current_wget_state;
+
+static char *image_url;
+static unsigned int wget_timeout = WGET_TIMEOUT;
+
+static enum net_loop_state wget_loop_state;
+
+/* Timeout retry parameters */
+static u8 retry_action; /* actions for TCP retry */
+static unsigned int retry_tcp_ack_num; /* TCP retry acknowledge number*/
+static unsigned int retry_tcp_seq_num; /* TCP retry sequence number */
+static int retry_len; /* TCP retry length */
+
+/**
+ * store_block() - store block in memory
+ * @src: source of data
+ * @offset: offset
+ * @len: length
+ */
+static inline int store_block(uchar *src, unsigned int offset, unsigned int len)
+{
+ ulong store_addr = image_load_addr + offset;
+ ulong newsize = offset + len;
+ uchar *ptr;
+
+ if (CONFIG_IS_ENABLED(LMB)) {
+ if (store_addr < image_load_addr ||
+ lmb_read_check(store_addr, len)) {
+ printf("\nwget error: ");
+ printf("trying to overwrite reserved memory...\n");
+ return -1;
+ }
+ }
+
+ ptr = map_sysmem(store_addr, len);
+ memcpy(ptr, src, len);
+ unmap_sysmem(ptr);
+
+ if (net_boot_file_size < (offset + len))
+ net_boot_file_size = newsize;
+
+ return 0;
+}
+
+/**
+ * wget_send_stored() - wget response dispatcher
+ *
+ * WARNING, This, and only this, is the place in wget.c where
+ * SEQUENCE NUMBERS are swapped between incoming (RX)
+ * and outgoing (TX).
+ * Procedure wget_handler() is correct for RX traffic.
+ */
+static void wget_send_stored(void)
+{
+ u8 action = retry_action;
+ int len = retry_len;
+ unsigned int tcp_ack_num = retry_tcp_seq_num + (len == 0 ? 1 : len);
+ unsigned int tcp_seq_num = retry_tcp_ack_num;
+ unsigned int server_port;
+ uchar *ptr, *offset;
+
+ server_port = env_get_ulong("httpdstp", 10, SERVER_PORT) & 0xffff;
+
+ switch (current_wget_state) {
+ case WGET_CLOSED:
+ debug_cond(DEBUG_WGET, "wget: send SYN\n");
+ current_wget_state = WGET_CONNECTING;
+ net_send_tcp_packet(0, server_port, our_port, action,
+ tcp_seq_num, tcp_ack_num);
+ packets = 0;
+ break;
+ case WGET_CONNECTING:
+ pkt_q_idx = 0;
+ net_send_tcp_packet(0, server_port, our_port, action,
+ tcp_seq_num, tcp_ack_num);
+
+ ptr = net_tx_packet + net_eth_hdr_size() +
+ IP_TCP_HDR_SIZE + TCP_TSOPT_SIZE + 2;
+ offset = ptr;
+
+ memcpy(offset, &bootfile1, strlen(bootfile1));
+ offset += strlen(bootfile1);
+
+ memcpy(offset, image_url, strlen(image_url));
+ offset += strlen(image_url);
+
+ memcpy(offset, &bootfile3, strlen(bootfile3));
+ offset += strlen(bootfile3);
+ net_send_tcp_packet((offset - ptr), server_port, our_port,
+ TCP_PUSH, tcp_seq_num, tcp_ack_num);
+ current_wget_state = WGET_CONNECTED;
+ break;
+ case WGET_CONNECTED:
+ case WGET_TRANSFERRING:
+ case WGET_TRANSFERRED:
+ net_send_tcp_packet(0, server_port, our_port, action,
+ tcp_seq_num, tcp_ack_num);
+ break;
+ }
+}
+
+static void wget_send(u8 action, unsigned int tcp_seq_num,
+ unsigned int tcp_ack_num, int len)
+{
+ retry_action = action;
+ retry_tcp_ack_num = tcp_ack_num;
+ retry_tcp_seq_num = tcp_seq_num;
+ retry_len = len;
+
+ wget_send_stored();
+}
+
+void wget_fail(char *error_message, unsigned int tcp_seq_num,
+ unsigned int tcp_ack_num, u8 action)
+{
+ printf("wget: Transfer Fail - %s\n", error_message);
+ net_set_timeout_handler(0, NULL);
+ wget_send(action, tcp_seq_num, tcp_ack_num, 0);
+}
+
+/*
+ * Interfaces of U-BOOT
+ */
+static void wget_timeout_handler(void)
+{
+ if (++wget_timeout_count > WGET_RETRY_COUNT) {
+ puts("\nRetry count exceeded; starting again\n");
+ wget_send(TCP_RST, 0, 0, 0);
+ net_start_again();
+ } else {
+ puts("T ");
+ net_set_timeout_handler(wget_timeout +
+ WGET_TIMEOUT * wget_timeout_count,
+ wget_timeout_handler);
+ wget_send_stored();
+ }
+}
+
+#define PKT_QUEUE_OFFSET 0x20000
+#define PKT_QUEUE_PACKET_SIZE 0x800
+
+static void wget_connected(uchar *pkt, unsigned int tcp_seq_num,
+ u8 action, unsigned int tcp_ack_num, unsigned int len)
+{
+ uchar *pkt_in_q;
+ char *pos;
+ int hlen, i;
+ uchar *ptr1;
+
+ pkt[len] = '\0';
+ pos = strstr((char *)pkt, http_eom);
+
+ if (!pos) {
+ debug_cond(DEBUG_WGET,
+ "wget: Connected, data before Header %p\n", pkt);
+ pkt_in_q = (void *)image_load_addr + PKT_QUEUE_OFFSET +
+ (pkt_q_idx * PKT_QUEUE_PACKET_SIZE);
+
+ ptr1 = map_sysmem((ulong)pkt_in_q, len);
+ memcpy(ptr1, pkt, len);
+ unmap_sysmem(ptr1);
+
+ pkt_q[pkt_q_idx].pkt = pkt_in_q;
+ pkt_q[pkt_q_idx].tcp_seq_num = tcp_seq_num;
+ pkt_q[pkt_q_idx].len = len;
+ pkt_q_idx++;
+
+ if (pkt_q_idx >= PKTQ_SZ) {
+ printf("wget: Fatal error, queue overrun!\n");
+ net_set_state(NETLOOP_FAIL);
+
+ return;
+ }
+ } else {
+ debug_cond(DEBUG_WGET, "wget: Connected HTTP Header %p\n", pkt);
+ /* sizeof(http_eom) - 1 is the string length of (http_eom) */
+ hlen = pos - (char *)pkt + sizeof(http_eom) - 1;
+ pos = strstr((char *)pkt, linefeed);
+ if (pos > 0)
+ i = pos - (char *)pkt;
+ else
+ i = hlen;
+ printf("%.*s", i, pkt);
+
+ current_wget_state = WGET_TRANSFERRING;
+
+ initial_data_seq_num = tcp_seq_num + hlen;
+ next_data_seq_num = tcp_seq_num + len;
+
+ if (strstr((char *)pkt, http_ok) == 0) {
+ debug_cond(DEBUG_WGET,
+ "wget: Connected Bad Xfer\n");
+ wget_loop_state = NETLOOP_FAIL;
+ wget_send(action, tcp_seq_num, tcp_ack_num, len);
+ } else {
+ debug_cond(DEBUG_WGET,
+ "wget: Connected Pkt %p hlen %x\n",
+ pkt, hlen);
+
+ pos = strstr((char *)pkt, content_len);
+ if (!pos) {
+ content_length = -1;
+ } else {
+ pos += sizeof(content_len) + 2;
+ strict_strtoul(pos, 10, &content_length);
+ debug_cond(DEBUG_WGET,
+ "wget: Connected Len %lu\n",
+ content_length);
+ }
+
+ net_boot_file_size = 0;
+
+ if (len > hlen) {
+ if (store_block(pkt + hlen, 0, len - hlen) != 0) {
+ wget_loop_state = NETLOOP_FAIL;
+ wget_fail("wget: store error\n", tcp_seq_num, tcp_ack_num, action);
+ net_set_state(NETLOOP_FAIL);
+ return;
+ }
+ }
+
+ for (i = 0; i < pkt_q_idx; i++) {
+ int err;
+
+ ptr1 = map_sysmem((ulong)pkt_q[i].pkt,
+ pkt_q[i].len);
+ err = store_block(ptr1,
+ pkt_q[i].tcp_seq_num -
+ initial_data_seq_num,
+ pkt_q[i].len);
+ unmap_sysmem(ptr1);
+ debug_cond(DEBUG_WGET,
+ "wget: Conncted pkt Q %p len %x\n",
+ pkt_q[i].pkt, pkt_q[i].len);
+ if (err) {
+ wget_loop_state = NETLOOP_FAIL;
+ wget_fail("wget: store error\n", tcp_seq_num, tcp_ack_num, action);
+ net_set_state(NETLOOP_FAIL);
+ return;
+ }
+ }
+ }
+ }
+ wget_send(action, tcp_seq_num, tcp_ack_num, len);
+}
+
+/**
+ * wget_handler() - TCP handler of wget
+ * @pkt: pointer to the application packet
+ * @dport: destination TCP port
+ * @sip: source IP address
+ * @sport: source TCP port
+ * @tcp_seq_num: TCP sequential number
+ * @tcp_ack_num: TCP acknowledgment number
+ * @action: TCP action (SYN, ACK, FIN, etc)
+ * @len: packet length
+ *
+ * In the "application push" invocation, the TCP header with all
+ * its information is pointed to by the packet pointer.
+ */
+static void wget_handler(uchar *pkt, u16 dport,
+ struct in_addr sip, u16 sport,
+ u32 tcp_seq_num, u32 tcp_ack_num,
+ u8 action, unsigned int len)
+{
+ enum tcp_state wget_tcp_state = tcp_get_tcp_state();
+
+ net_set_timeout_handler(wget_timeout, wget_timeout_handler);
+ packets++;
+
+ switch (current_wget_state) {
+ case WGET_CLOSED:
+ debug_cond(DEBUG_WGET, "wget: Handler: Error!, State wrong\n");
+ break;
+ case WGET_CONNECTING:
+ debug_cond(DEBUG_WGET,
+ "wget: Connecting In len=%x, Seq=%u, Ack=%u\n",
+ len, tcp_seq_num, tcp_ack_num);
+ if (!len) {
+ if (wget_tcp_state == TCP_ESTABLISHED) {
+ debug_cond(DEBUG_WGET,
+ "wget: Cting, send, len=%x\n", len);
+ wget_send(action, tcp_seq_num, tcp_ack_num,
+ len);
+ } else {
+ printf("%.*s", len, pkt);
+ wget_fail("wget: Handler Connected Fail\n",
+ tcp_seq_num, tcp_ack_num, action);
+ }
+ }
+ break;
+ case WGET_CONNECTED:
+ debug_cond(DEBUG_WGET, "wget: Connected seq=%u, len=%x\n",
+ tcp_seq_num, len);
+ if (!len) {
+ wget_fail("Image not found, no data returned\n",
+ tcp_seq_num, tcp_ack_num, action);
+ } else {
+ wget_connected(pkt, tcp_seq_num, action, tcp_ack_num, len);
+ }
+ break;
+ case WGET_TRANSFERRING:
+ debug_cond(DEBUG_WGET,
+ "wget: Transferring, seq=%x, ack=%x,len=%x\n",
+ tcp_seq_num, tcp_ack_num, len);
+
+ if (next_data_seq_num != tcp_seq_num) {
+ debug_cond(DEBUG_WGET, "wget: seq=%x packet was lost\n", next_data_seq_num);
+ return;
+ }
+ next_data_seq_num = tcp_seq_num + len;
+
+ if (store_block(pkt, tcp_seq_num - initial_data_seq_num, len) != 0) {
+ wget_fail("wget: store error\n",
+ tcp_seq_num, tcp_ack_num, action);
+ net_set_state(NETLOOP_FAIL);
+ return;
+ }
+
+ switch (wget_tcp_state) {
+ case TCP_FIN_WAIT_2:
+ wget_send(TCP_ACK, tcp_seq_num, tcp_ack_num, len);
+ fallthrough;
+ case TCP_SYN_SENT:
+ case TCP_SYN_RECEIVED:
+ case TCP_CLOSING:
+ case TCP_FIN_WAIT_1:
+ case TCP_CLOSED:
+ net_set_state(NETLOOP_FAIL);
+ break;
+ case TCP_ESTABLISHED:
+ wget_send(TCP_ACK, tcp_seq_num, tcp_ack_num,
+ len);
+ wget_loop_state = NETLOOP_SUCCESS;
+ break;
+ case TCP_CLOSE_WAIT: /* End of transfer */
+ current_wget_state = WGET_TRANSFERRED;
+ wget_send(action | TCP_ACK | TCP_FIN,
+ tcp_seq_num, tcp_ack_num, len);
+ break;
+ }
+ break;
+ case WGET_TRANSFERRED:
+ printf("Packets received %d, Transfer Successful\n", packets);
+ net_set_state(wget_loop_state);
+ efi_set_bootdev("Net", "", image_url,
+ map_sysmem(image_load_addr, 0),
+ net_boot_file_size);
+ env_set_hex("filesize", net_boot_file_size);
+ break;
+ }
+}
+
+#define RANDOM_PORT_START 1024
+#define RANDOM_PORT_RANGE 0x4000
+
+/**
+ * random_port() - make port a little random (1024-17407)
+ *
+ * Return: random port number from 1024 to 17407
+ *
+ * This keeps the math somewhat trivial to compute, and seems to work with
+ * all supported protocols/clients/servers
+ */
+static unsigned int random_port(void)
+{
+ return RANDOM_PORT_START + (get_timer(0) % RANDOM_PORT_RANGE);
+}
+
+#define BLOCKSIZE 512
+
+void wget_start(void)
+{
+ image_url = strchr(net_boot_file_name, ':');
+ if (image_url > 0) {
+ web_server_ip = string_to_ip(net_boot_file_name);
+ ++image_url;
+ net_server_ip = web_server_ip;
+ } else {
+ web_server_ip = net_server_ip;
+ image_url = net_boot_file_name;
+ }
+
+ debug_cond(DEBUG_WGET,
+ "wget: Transfer HTTP Server %pI4; our IP %pI4\n",
+ &web_server_ip, &net_ip);
+
+ /* Check if we need to send across this subnet */
+ if (net_gateway.s_addr && net_netmask.s_addr) {
+ struct in_addr our_net;
+ struct in_addr server_net;
+
+ our_net.s_addr = net_ip.s_addr & net_netmask.s_addr;
+ server_net.s_addr = net_server_ip.s_addr & net_netmask.s_addr;
+ if (our_net.s_addr != server_net.s_addr)
+ debug_cond(DEBUG_WGET,
+ "wget: sending through gateway %pI4",
+ &net_gateway);
+ }
+ debug_cond(DEBUG_WGET, "URL '%s'\n", image_url);
+
+ if (net_boot_file_expected_size_in_blocks) {
+ debug_cond(DEBUG_WGET, "wget: Size is 0x%x Bytes = ",
+ net_boot_file_expected_size_in_blocks * BLOCKSIZE);
+ print_size(net_boot_file_expected_size_in_blocks * BLOCKSIZE,
+ "");
+ }
+ debug_cond(DEBUG_WGET,
+ "\nwget:Load address: 0x%lx\nLoading: *\b", image_load_addr);
+
+ net_set_timeout_handler(wget_timeout, wget_timeout_handler);
+ tcp_set_tcp_handler(wget_handler);
+
+ wget_timeout_count = 0;
+ current_wget_state = WGET_CLOSED;
+
+ our_port = random_port();
+
+ /*
+ * Zero out server ether to force arp resolution in case
+ * the server ip for the previous u-boot command, for example dns
+ * is not the same as the web server ip.
+ */
+
+ memset(net_server_ethaddr, 0, 6);
+
+ wget_send(TCP_SYN, 0, 0, 0);
+}
+
+#if (IS_ENABLED(CONFIG_CMD_DNS))
+int wget_with_dns(ulong dst_addr, char *uri)
+{
+ int ret;
+ char *s, *host_name, *file_name, *str_copy;
+
+ /*
+ * Download file using wget.
+ *
+ * U-Boot wget takes the target uri in this format.
+ * "<http server ip>:<file path>" e.g.) 192.168.1.1:/sample/test.iso
+ * Need to resolve the http server ip address before starting wget.
+ */
+ str_copy = strdup(uri);
+ if (!str_copy)
+ return -ENOMEM;
+
+ s = str_copy + strlen("http://");
+ host_name = strsep(&s, "/");
+ if (!s) {
+ log_err("Error: invalied uri, no file path\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ file_name = s;
+
+ /* TODO: If the given uri has ip address for the http server, skip dns */
+ net_dns_resolve = host_name;
+ net_dns_env_var = "httpserverip";
+ if (net_loop(DNS) < 0) {
+ log_err("Error: dns lookup of %s failed, check setup\n", net_dns_resolve);
+ ret = -EINVAL;
+ goto out;
+ }
+ s = env_get("httpserverip");
+ if (!s) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ strlcpy(net_boot_file_name, s, sizeof(net_boot_file_name));
+ strlcat(net_boot_file_name, ":/", sizeof(net_boot_file_name)); /* append '/' which is removed by strsep() */
+ strlcat(net_boot_file_name, file_name, sizeof(net_boot_file_name));
+ image_load_addr = dst_addr;
+ ret = net_loop(WGET);
+
+out:
+ free(str_copy);
+
+ return ret;
+}
+#endif
+
+/**
+ * wget_validate_uri() - validate the uri for wget
+ *
+ * @uri: uri string
+ *
+ * This function follows the current U-Boot wget implementation.
+ * scheme: only "http:" is supported
+ * authority:
+ * - user information: not supported
+ * - host: supported
+ * - port: not supported(always use the default port)
+ *
+ * Uri is expected to be correctly percent encoded.
+ * This is the minimum check, control codes(0x1-0x19, 0x7F, except '\0')
+ * and space character(0x20) are not allowed.
+ *
+ * TODO: stricter uri conformance check
+ *
+ * Return: true on success, false on failure
+ */
+bool wget_validate_uri(char *uri)
+{
+ char c;
+ bool ret = true;
+ char *str_copy, *s, *authority;
+
+ for (c = 0x1; c < 0x21; c++) {
+ if (strchr(uri, c)) {
+ log_err("invalid character is used\n");
+ return false;
+ }
+ }
+ if (strchr(uri, 0x7f)) {
+ log_err("invalid character is used\n");
+ return false;
+ }
+
+ if (strncmp(uri, "http://", 7)) {
+ log_err("only http:// is supported\n");
+ return false;
+ }
+ str_copy = strdup(uri);
+ if (!str_copy)
+ return false;
+
+ s = str_copy + strlen("http://");
+ authority = strsep(&s, "/");
+ if (!s) {
+ log_err("invalid uri, no file path\n");
+ ret = false;
+ goto out;
+ }
+ s = strchr(authority, '@');
+ if (s) {
+ log_err("user information is not supported\n");
+ ret = false;
+ goto out;
+ }
+ s = strchr(authority, ':');
+ if (s) {
+ log_err("user defined port is not supported\n");
+ ret = false;
+ goto out;
+ }
+
+out:
+ free(str_copy);
+
+ return ret;
+}
diff --git a/net/wol.c b/net/wol.c
new file mode 100644
index 00000000000..96478ba5751
--- /dev/null
+++ b/net/wol.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2018 Lothar Felten, lothar.felten@gmail.com
+ */
+
+#include <command.h>
+#include <env.h>
+#include <net.h>
+#include "wol.h"
+
+static ulong wol_timeout = WOL_DEFAULT_TIMEOUT;
+
+/*
+ * Check incoming Wake-on-LAN packet for:
+ * - sync bytes
+ * - sixteen copies of the target MAC address
+ *
+ * @param wol Wake-on-LAN packet
+ * @param len Packet length
+ */
+static int wol_check_magic(struct wol_hdr *wol, unsigned int len)
+{
+ int i;
+
+ if (len < sizeof(struct wol_hdr))
+ return 0;
+
+ for (i = 0; i < WOL_SYNC_COUNT; i++)
+ if (wol->wol_sync[i] != WOL_SYNC_BYTE)
+ return 0;
+
+ for (i = 0; i < WOL_MAC_REPETITIONS; i++)
+ if (memcmp(&wol->wol_dest[i * ARP_HLEN],
+ net_ethaddr, ARP_HLEN) != 0)
+ return 0;
+
+ return 1;
+}
+
+void wol_receive(struct ip_udp_hdr *ip, unsigned int len)
+{
+ struct wol_hdr *wol;
+
+ wol = (struct wol_hdr *)ip;
+
+ if (!wol_check_magic(wol, len))
+ return;
+
+ /* save the optional password using the ether-wake formats */
+ /* don't check for exact length, the packet might have padding */
+ if (len >= (sizeof(struct wol_hdr) + WOL_PASSWORD_6B)) {
+ eth_env_set_enetaddr("wolpassword", wol->wol_passwd);
+ } else if (len >= (sizeof(struct wol_hdr) + WOL_PASSWORD_4B)) {
+ char buffer[16];
+ struct in_addr *ip = (struct in_addr *)(wol->wol_passwd);
+
+ ip_to_string(*ip, buffer);
+ env_set("wolpassword", buffer);
+ }
+ net_set_state(NETLOOP_SUCCESS);
+}
+
+static void wol_udp_handler(uchar *pkt, unsigned int dest, struct in_addr sip,
+ unsigned int src, unsigned int len)
+{
+ struct wol_hdr *wol;
+
+ wol = (struct wol_hdr *)pkt;
+
+ /* UDP destination port must be 0, 7 or 9 */
+ if (dest != 0 && dest != 7 && dest != 9)
+ return;
+
+ if (!wol_check_magic(wol, len))
+ return;
+
+ net_set_state(NETLOOP_SUCCESS);
+}
+
+void wol_set_timeout(ulong timeout)
+{
+ wol_timeout = timeout;
+}
+
+static void wol_timeout_handler(void)
+{
+ eth_halt();
+ net_set_state(NETLOOP_FAIL);
+}
+
+void wol_start(void)
+{
+ net_set_timeout_handler(wol_timeout, wol_timeout_handler);
+ net_set_udp_handler(wol_udp_handler);
+}
diff --git a/net/wol.h b/net/wol.h
new file mode 100644
index 00000000000..ebc81f24b6e
--- /dev/null
+++ b/net/wol.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * wol - Wake-on-LAN
+ *
+ * Supports both Wake-on-LAN packet types:
+ * - EtherType 0x0842 packets
+ * - UDP packets on ports 0, 7 and 9.
+ *
+ * Copyright 2018 Lothar Felten, lothar.felten@gmail.com
+ */
+
+#if defined(CONFIG_CMD_WOL)
+
+#ifndef __WOL_H__
+#define __WOL_H__
+
+#include <net.h>
+
+/**********************************************************************/
+
+#define WOL_SYNC_BYTE 0xFF
+#define WOL_SYNC_COUNT 6
+#define WOL_MAC_REPETITIONS 16
+#define WOL_DEFAULT_TIMEOUT 5000
+#define WOL_PASSWORD_4B 4
+#define WOL_PASSWORD_6B 6
+
+/*
+ * Wake-on-LAN header
+ */
+struct wol_hdr {
+ u8 wol_sync[WOL_SYNC_COUNT]; /* sync bytes */
+ u8 wol_dest[WOL_MAC_REPETITIONS * ARP_HLEN]; /* 16x MAC */
+ u8 wol_passwd[0]; /* optional */
+};
+
+/*
+ * Initialize wol (beginning of netloop)
+ */
+void wol_start(void);
+
+/*
+ * Check incoming Wake-on-LAN packet for:
+ * - sync bytes
+ * - sixteen copies of the target MAC address
+ *
+ * Optionally store the four or six byte password in the environment
+ * variable "wolpassword"
+ *
+ * @param ip IP header in the packet
+ * @param len Packet length
+ */
+void wol_receive(struct ip_udp_hdr *ip, unsigned int len);
+
+/*
+ * Set the timeout for the reception of a Wake-on-LAN packet
+ *
+ * @param timeout in milliseconds
+ */
+void wol_set_timeout(ulong timeout);
+
+/**********************************************************************/
+
+#endif /* __WOL_H__ */
+#endif