diff options
Diffstat (limited to 'net')
-rw-r--r-- | net/Kconfig | 265 | ||||
-rw-r--r-- | net/Makefile | 40 | ||||
-rw-r--r-- | net/arp.c | 230 | ||||
-rw-r--r-- | net/arp.h | 30 | ||||
-rw-r--r-- | net/bootp.c | 1163 | ||||
-rw-r--r-- | net/bootp.h | 95 | ||||
-rw-r--r-- | net/cdp.c | 359 | ||||
-rw-r--r-- | net/cdp.h | 22 | ||||
-rw-r--r-- | net/dhcpv6.c | 732 | ||||
-rw-r--r-- | net/dhcpv6.h | 256 | ||||
-rw-r--r-- | net/dns.c | 217 | ||||
-rw-r--r-- | net/dns.h | 35 | ||||
-rw-r--r-- | net/dsa-uclass.c | 533 | ||||
-rw-r--r-- | net/eth-uclass.c | 657 | ||||
-rw-r--r-- | net/eth_bootdev.c | 118 | ||||
-rw-r--r-- | net/eth_common.c | 137 | ||||
-rw-r--r-- | net/eth_internal.h | 19 | ||||
-rw-r--r-- | net/fastboot_tcp.c | 145 | ||||
-rw-r--r-- | net/fastboot_udp.c | 305 | ||||
-rw-r--r-- | net/link_local.c | 348 | ||||
-rw-r--r-- | net/link_local.h | 21 | ||||
-rw-r--r-- | net/mdio-mux-uclass.c | 224 | ||||
-rw-r--r-- | net/mdio-uclass.c | 323 | ||||
-rw-r--r-- | net/ndisc.c | 515 | ||||
-rw-r--r-- | net/net.c | 1780 | ||||
-rw-r--r-- | net/net6.c | 449 | ||||
-rw-r--r-- | net/net_rand.h | 59 | ||||
-rw-r--r-- | net/nfs.c | 984 | ||||
-rw-r--r-- | net/nfs.h | 86 | ||||
-rw-r--r-- | net/pcap.c | 156 | ||||
-rw-r--r-- | net/ping.c | 119 | ||||
-rw-r--r-- | net/ping.h | 31 | ||||
-rw-r--r-- | net/ping6.c | 117 | ||||
-rw-r--r-- | net/rarp.c | 91 | ||||
-rw-r--r-- | net/rarp.h | 28 | ||||
-rw-r--r-- | net/sntp.c | 123 | ||||
-rw-r--r-- | net/tcp.c | 718 | ||||
-rw-r--r-- | net/tftp.c | 989 | ||||
-rw-r--r-- | net/udp.c | 45 | ||||
-rw-r--r-- | net/wget.c | 606 | ||||
-rw-r--r-- | net/wol.c | 95 | ||||
-rw-r--r-- | net/wol.h | 65 |
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", ðernet)) + 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", ®)) + 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 = ð_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 |