From bc6e0f462ee66ee21bbb588725685ee40c6f8ee4 Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Wed, 2 Jan 2019 17:37:49 -0800 Subject: [PATCH] last known wget --- load-kernel-and-initrd.cmd | 29 + ...-Consolidating-UDP-header-functions..patch | 1957 +++++++++++++++++ u-boot-patches/U-Boot-v12-2-3-Add-TCP.diff | 1085 +++++++++ .../U-Boot-v12-3-3-Add-wget-application.diff | 560 +++++ u-boot/bl31.bin | Bin 0 -> 32776 bytes 5 files changed, 3631 insertions(+) create mode 100755 load-kernel-and-initrd.cmd create mode 100644 u-boot-patches/U-Boot-v12-1-3-Consolidating-UDP-header-functions..patch create mode 100644 u-boot-patches/U-Boot-v12-2-3-Add-TCP.diff create mode 100644 u-boot-patches/U-Boot-v12-3-3-Add-wget-application.diff create mode 100755 u-boot/bl31.bin diff --git a/load-kernel-and-initrd.cmd b/load-kernel-and-initrd.cmd new file mode 100755 index 0000000..edaf63c --- /dev/null +++ b/load-kernel-and-initrd.cmd @@ -0,0 +1,29 @@ +setenv bootmenu_0 coninfo=coninfo +setenv bootmenu_1 mmcinfo=mmcinfo +bootmenu 3 + +printenv mmc_boot +printenv scan_dev_for_boot_part +printenv scan_dev_for_boot +printenv scan_dev_for_scripts +printenv boot_scripts +boot_scripts=boot.scr.uimg boot.scr +printenv boot_a_script +boot_a_script=load ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} ${prefix}${script}; source ${scriptaddr} + +ext4load mmc 1:1 ${scriptaddr} /boot/sid.scr +source ${scriptaddr} + +ext4ls mmc 1:1 /boot +# ext4load mmc 1:1 0x42000000 /root/hello_world + +setenv bootargs 'root=/dev/mmcblk2p1 rootwait rootfstype=ext4 console=ttyS0,115200 console=tty1 panic=10 consoleblank=0 loglevel=1 ubootpart=6526ddf3-01 usb-storage.quirks=0x2537:0x1066:u,0x2537:0x1068:u init=3 cgroup_enable=memory swapaccount=1 3' + +setenv bootargs 'root=/dev/mmcblk2p1 rootwait console=ttyS0,115200 console=tty1' + +ext4load mmc 1:1 ${ramdisk_addr_r} /boot/uInitrd-4.19.2-sunxi64 +ext4load mmc 1:1 ${kernel_addr_r} /boot/vmlinuz-4.19.2-sunxi64 + +booti ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r} + +bootelf diff --git a/u-boot-patches/U-Boot-v12-1-3-Consolidating-UDP-header-functions..patch b/u-boot-patches/U-Boot-v12-1-3-Consolidating-UDP-header-functions..patch new file mode 100644 index 0000000..dc7ce9b --- /dev/null +++ b/u-boot-patches/U-Boot-v12-1-3-Consolidating-UDP-header-functions..patch @@ -0,0 +1,1957 @@ +From patchwork Sun Jun 24 22:40:41 2018 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [U-Boot,v12,1/3] Consolidating UDP header functions. +X-Patchwork-Submitter: Duncan Hare +X-Patchwork-Id: 934006 +X-Patchwork-Delegate: joe.hershberger@gmail.com +Message-Id: <20180624224043.31503-1-DH@synoia.com> +To: DuncanCHare@yahoo.com +Cc: Duncan Hare , Joe Hershberger , + u-boot@lists.denx.de, Jason Kridner , + Jocelyn Bohr +Date: Sun, 24 Jun 2018 15:40:41 -0700 +From: DH@synoia.com +List-Id: U-Boot discussion + +From: Duncan Hare + +To make it possible to add TCP versions of the same, while reusing +IP portions. This patch should not change any behavior. + +All references to TCP removed +Used most recent version of u-boot June 22 13, 2918 +Series to fix patman errors over Licensing declaration +END + +Series-notes +TCP with Selective Acknowledgment (SACK) is currently the protocol +with highest speed transfers, for fast multi-hop networks. +END + +Signed-off-by: Duncan Hare +Signed-off-by: Duncan Hare +--- + + include/net.h | 6 +++++- + net/net.c | 34 ++++++++++++++++++++++++---------- + net/ping.c | 7 +------ + 3 files changed, 30 insertions(+), 17 deletions(-) + +diff --git a/include/net.h b/include/net.h +index 5760685556..a54160fff6 100644 +--- a/include/net.h ++++ b/include/net.h +@@ -593,7 +593,8 @@ int net_set_ether(uchar *xet, const uchar *dest_ethaddr, uint prot); + int net_update_ether(struct ethernet_hdr *et, uchar *addr, uint prot); + + /* Set IP header */ +-void net_set_ip_header(uchar *pkt, struct in_addr dest, struct in_addr source); ++void net_set_ip_header(uchar *pkt, struct in_addr dest, struct in_addr source, ++ u16 pkt_len, u8 proto); + void net_set_udp_header(uchar *pkt, struct in_addr dest, int dport, + int sport, int len); + +@@ -667,6 +668,9 @@ static inline void net_send_packet(uchar *pkt, int len) + * @param sport Source UDP port + * @param payload_len Length of data after the UDP header + */ ++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); + int net_send_udp_packet(uchar *ether, struct in_addr dest, int dport, + int sport, int payload_len); + +diff --git a/net/net.c b/net/net.c +index b4563a4cab..f831c34599 100644 +--- a/net/net.c ++++ b/net/net.c +@@ -786,6 +786,14 @@ void net_set_timeout_handler(ulong iv, thand_f *f) + 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); ++} ++ ++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; +@@ -806,9 +814,15 @@ int net_send_udp_packet(uchar *ether, struct in_addr dest, int dport, int sport, + pkt = (uchar *)net_tx_packet; + + eth_hdr_size = net_set_ether(pkt, ether, PROT_IP); +- pkt += eth_hdr_size; +- net_set_udp_header(pkt, dest, dport, sport, payload_len); +- pkt_hdr_size = eth_hdr_size + IP_UDP_HDR_SIZE; ++ ++ 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; ++ default: return -EINVAL; ++ } + + /* if MAC address was not discovered yet, do an ARP request */ + if (memcmp(ether, net_null_ethaddr, 6) == 0) { +@@ -1434,7 +1448,8 @@ int net_update_ether(struct ethernet_hdr *et, uchar *addr, uint prot) + } + } + +-void net_set_ip_header(uchar *pkt, struct in_addr dest, struct in_addr source) ++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; + +@@ -1444,11 +1459,12 @@ void net_set_ip_header(uchar *pkt, struct in_addr dest, struct in_addr source) + /* IP_HDR_SIZE / 4 (not including UDP) */ + ip->ip_hl_v = 0x45; + ip->ip_tos = 0; +- ip->ip_len = htons(IP_HDR_SIZE); ++ 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; ++ ip->ip_sum = compute_ip_checksum(ip, IP_HDR_SIZE); + /* already in network byte order */ + net_copy_ip((void *)&ip->ip_src, &source); + /* already in network byte order */ +@@ -1468,10 +1484,8 @@ void net_set_udp_header(uchar *pkt, struct in_addr dest, int dport, int sport, + if (len & 1) + pkt[IP_UDP_HDR_SIZE + len] = 0; + +- net_set_ip_header(pkt, dest, net_ip); +- ip->ip_len = htons(IP_UDP_HDR_SIZE + len); +- ip->ip_p = IPPROTO_UDP; +- ip->ip_sum = compute_ip_checksum(ip, IP_HDR_SIZE); ++ 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); +diff --git a/net/ping.c b/net/ping.c +index 3e5461a36a..d5d914bf2a 100644 +--- a/net/ping.c ++++ b/net/ping.c +@@ -22,14 +22,9 @@ static void set_icmp_header(uchar *pkt, struct in_addr dest) + /* + * Construct an IP and ICMP header. + */ +- struct ip_hdr *ip = (struct ip_hdr *)pkt; + struct icmp_hdr *icmp = (struct icmp_hdr *)(pkt + IP_HDR_SIZE); + +- net_set_ip_header(pkt, dest, net_ip); +- +- ip->ip_len = htons(IP_ICMP_HDR_SIZE); +- ip->ip_p = IPPROTO_ICMP; +- ip->ip_sum = compute_ip_checksum(ip, 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; + +From patchwork Sun Jun 24 22:40:42 2018 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [U-Boot,v12,2/3] Add TCP +X-Patchwork-Submitter: Duncan Hare +X-Patchwork-Id: 934008 +X-Patchwork-Delegate: joe.hershberger@gmail.com +Message-Id: <20180624224043.31503-2-DH@synoia.com> +To: DuncanCHare@yahoo.com +Cc: Duncan Hare , Joe Hershberger , + u-boot@lists.denx.de, Jason Kridner +Date: Sun, 24 Jun 2018 15:40:42 -0700 +From: DH@synoia.com +List-Id: U-Boot discussion + +From: Duncan Hare + +Currently file transfers are done using tftp or NFS both +over udp. This requires a request to be sent from client +(u-boot) to the boot server. + +The current standard is TCP with selective acknowledgment. + +In our testing we have reduce kernel transmission time to +around 0.4 seconds for a 4Mbyte kernel, with a 100 Mbps +downlink. + +Series-Changes 11 +- Add TCP with SACK +- Clean formatting +- Remove buffer search and print routines + +Series-Changes 12 +- Fix License statement + +Signed-off-by: Duncan Hare +Signed-off-by: Duncan Hare +--- + + include/net.h | 11 +- + include/net/tcp.h | 227 ++++++++++++++++++ + net/Kconfig | 6 + + net/Makefile | 1 + + net/net.c | 33 +++ + net/tcp.c | 700 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 6 files changed, 977 insertions(+), 1 deletion(-) + create mode 100644 include/net/tcp.h + create mode 100644 net/tcp.c + +diff --git a/include/net.h b/include/net.h +index a54160fff6..ba96267eb8 100644 +--- a/include/net.h ++++ b/include/net.h +@@ -26,6 +26,9 @@ + * + */ + ++#if defined(CONFIG_TCP) /* Protected UDP uses less bufferes than TCP */ ++#define CONFIG_SYS_RX_ETH_BUFFER 12 ++#endif + #ifdef CONFIG_SYS_RX_ETH_BUFFER + # define PKTBUFSRX CONFIG_SYS_RX_ETH_BUFFER + #else +@@ -350,6 +353,7 @@ struct vlan_ethernet_hdr { + #define PROT_PPP_SES 0x8864 /* PPPoE session messages */ + + #define IPPROTO_ICMP 1 /* Internet Control Message Protocol */ ++#define IPPROTO_TCP 6 /* Transmission Control Protocol */ + #define IPPROTO_UDP 17 /* User Datagram Protocol */ + + /* +@@ -659,7 +663,7 @@ static inline void net_send_packet(uchar *pkt, int len) + } + + /* +- * Transmit "net_tx_packet" as UDP packet, performing ARP request if needed ++ * Transmit "net_tx_packet" as UDP or TCPpacket, send ARP request if needed + * (ether will be populated) + * + * @param ether Raw packet buffer +@@ -667,10 +671,15 @@ static inline void net_send_packet(uchar *pkt, int len) + * @param dport Destination UDP port + * @param sport Source UDP port + * @param payload_len Length of data after the UDP header ++ * @param action TCP action to be performed ++ * @param tcp_seq_num TCP sequence number of this transmission ++ * @param tcp_ack_num TCP stream acknolegement number + */ + 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); ++int net_send_tcp_packet(int payload_len, int dport, int sport, u8 action, ++ u32 tcp_seq_num, u32 tcp_ack_num); + int net_send_udp_packet(uchar *ether, struct in_addr dest, int dport, + int sport, int payload_len); + +diff --git a/include/net/tcp.h b/include/net/tcp.h +new file mode 100644 +index 0000000000..d0e90e07dd +--- /dev/null ++++ b/include/net/tcp.h +@@ -0,0 +1,227 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * TCP Support with SACK for file transfer. ++ * ++ * Copyright 2017 Duncan Hare, All rights reserved. ++ */ ++ ++#define TCP_ACTIVITY 127 /* Number of packets received */ ++ /* before console progress mark */ ++ ++struct ip_tcp_hdr { ++ u8 ip_hl_v; /* header length and version */ ++ u8 ip_tos; /* type of service */ ++ u16 ip_len; /* total length */ ++ u16 ip_id; /* identification */ ++ u16 ip_off; /* fragment offset field */ ++ u8 ip_ttl; /* time to live */ ++ u8 ip_p; /* protocol */ ++ u16 ip_sum; /* checksum */ ++ struct in_addr ip_src; /* Source IP address */ ++ struct in_addr ip_dst; /* Destination IP address */ ++ u16 tcp_src; /* TCP source port */ ++ u16 tcp_dst; /* TCP destination port */ ++ u32 tcp_seq; /* TCP sequence number */ ++ u32 tcp_ack; /* TCP Acknowledgment number */ ++ u8 tcp_hlen; /* 4 bits TCP header Length/4 */ ++ /* 4 bits Reserved */ ++ /* 2 more bits reserved */ ++ u8 tcp_flags; /* see defines */ ++ u16 tcp_win; /* TCP windows size */ ++ u16 tcp_xsum; /* Checksum */ ++ u16 tcp_ugr; /* Pointer to urgent data */ ++} __packed; ++ ++#define IP_TCP_HDR_SIZE (sizeof(struct ip_tcp_hdr)) ++#define TCP_HDR_SIZE (IP_TCP_HDR_SIZE - IP_HDR_SIZE) ++ ++#define TCP_DATA 0x00 /* Data Packet - internal use only */ ++#define TCP_FIN 0x01 /* Finish flag */ ++#define TCP_SYN 0x02 /* Synch (start) flag */ ++#define TCP_RST 0x04 /* reset flag */ ++#define TCP_PUSH 0x08 /* Push - Notify app */ ++#define TCP_ACK 0x10 /* Acknowledgment of data received */ ++#define TCP_URG 0x20 /* Urgent */ ++#define TCP_ECE 0x40 /* Congestion control */ ++#define TCP_CWR 0x80 /* Congestion Control */ ++ ++/* ++ * TCP header options, Seq, MSS, and SACK ++ */ ++ ++#define TCP_SACK 32 /* Number of packets analyzed */ ++ /* on leading edge of stream */ ++ ++#define TCP_O_END 0x00 /* End of option list */ ++#define TCP_1_NOP 0x01 /* Single padding NOP */ ++#define TCP_O_NOP 0x01010101 /* NOPs pad to 32 bit boundary */ ++#define TCP_O_MSS 0x02 /* MSS Size option */ ++#define TCP_O_SCL 0x03 /* Window Scale option */ ++#define TCP_P_SACK 0x04 /* SACK permitted */ ++#define TCP_V_SACK 0x05 /* SACK values */ ++#define TCP_O_TS 0x08 /* Timestamp option */ ++#define TCP_OPT_LEN_2 0x02 ++#define TCP_OPT_LEN_3 0x03 ++#define TCP_OPT_LEN_4 0x04 ++#define TCP_OPT_LEN_6 0x06 ++#define TCP_OPT_LEN_8 0x08 ++#define TCP_OPT_LEN_A 0x0a /* Timestamp Length */ ++ ++/* ++ * Please review the warning in net.c about these two parameters. ++ * They are part of a promise of RX buffer size to the sending TCP ++ */ ++ ++#define TCP_MSS 1460 /* Max segment size */ ++#define TCP_SCALE 0x01 /* Scale */ ++ ++struct tcp_mss { /* TCP Max Segment size */ ++ u8 kind; /* Field ID */ ++ u8 len; /* Field length */ ++ u16 mss; /* Segment size value */ ++} __packed; ++ ++struct tcp_scale { /* TCP Windows Scale */ ++ u8 kind; /* Field ID */ ++ u8 len; /* Filed length */ ++ u8 scale; /* windows shift value used for */ ++ /* networks with many hops */ ++ /* Typically 4 or more hops */ ++ ++} __packed; ++ ++struct tcp_sack_p { /* SACK permitted */ ++ u8 kind; /* Field Id */ ++ u8 len; /* Field length */ ++} __packed; ++ ++ /* Terse definitions used */ ++ /* long definitions make the */ ++ /* indented code overflow line */ ++ /* length linits */ ++struct sack_edges { ++ u32 l; /* Left edge of stream */ ++ u32 r; /* right edge of stream */ ++} __packed; ++ ++#define TCP_SACK_SIZE (sizeof(struct sack_edges)) ++ ++/* ++ * A TCP stream has holes when packets are missing or disordered. ++ * A hill is the inverese of a hole, and is data received. ++ * TCP receiveds hills (a sequence of data), and inferrs Holes ++ * from the "hills" or packets received. ++ */ ++ ++#define TCP_SACK_HILLS 4 ++ ++struct tcp_sack_v { ++ u8 kind; /* Field ID */ ++ u8 len; /* Field Length */ ++ struct sack_edges hill[TCP_SACK_HILLS]; /* L & R window edges */ ++} __packed; ++ ++struct tcp_t_opt { /* TCP time stamps option */ ++ u8 kind; /* Field id */ ++ u8 len; /* Field length */ ++ u32 t_snd; /* Sender timestamp */ ++ u32 t_rcv; /* Receiver timestamp */ ++} __packed; ++ ++#define TCP_TSOPT_SIZE (sizeof(struct tcp_t_opt)) ++ ++/* ++ * ip tcp structure with options ++ */ ++ ++struct ip_tcp_hdr_o { ++ struct ip_tcp_hdr hdr; ++ struct tcp_mss mss; ++ struct tcp_scale scale; ++ struct tcp_sack_p sack_p; ++ struct tcp_t_opt t_opt; ++ u8 end; ++} __packed; ++ ++#define IP_TCP_O_SIZE (sizeof(struct ip_tcp_hdr_o)) ++ ++struct ip_tcp_hdr_s { ++ struct ip_tcp_hdr hdr; ++ struct tcp_t_opt t_opt; ++ struct tcp_sack_v sack_v; ++ u8 end; ++} __packed; ++ ++#define IP_TCP_SACK_SIZE (sizeof(struct ip_tcp_hdr_s)) ++ ++/* ++ * TCP pseudo header definitions ++ */ ++#define PSEUDO_PAD_SIZE 8 ++ ++struct pseudo_hdr { ++ u8 padding[PSEUDO_PAD_SIZE]; /* pseudo hdr size = ip_tcp hdr size */ ++ struct in_addr p_src; ++ struct in_addr p_dst; ++ u8 rsvd; ++ u8 p; ++ u16 len; ++} __packed; ++ ++#define PSEUDO_HDR_SIZE (sizeof(struct pseudo_hdr)) - PSEUDO_PAD_SIZE ++ ++/* ++ * union for building TCP/IP packet. Build Pseudo header in packed buffer ++ * first, calculate TCP checksum, then build IP header in packed buffer. ++ */ ++ ++union tcp_build_pkt { ++ struct pseudo_hdr ph; ++ struct ip_tcp_hdr_o ip; ++ struct ip_tcp_hdr_s sack; ++ uchar raw[1600]; ++} __packed; ++ ++/* ++ * TCP State machine states for connection ++ */ ++ ++enum TCP_STATE { ++ TCP_CLOSED, /* Need to send SYN to connect */ ++ TCP_SYN_SENT, /* Trying to connect, waiting for SYN ACK */ ++ TCP_ESTABLISHED, /* both server & client have a connection */ ++ TCP_CLOSE_WAIT, /* Rec FIN, passed to app for FIN, ACK rsp*/ ++ TCP_CLOSING, /* Rec FIN, sent FIN, ACK waiting for ACK */ ++ TCP_FIN_WAIT_1, /* Sent FIN waiting for response */ ++ TCP_FIN_WAIT_2 /* Rec ACK from FIN sent, waiting for FIN */ ++}; ++ ++enum TCP_STATE tcp_get_tcp_state(void); ++void tcp_set_tcp_state(enum TCP_STATE new_state); ++int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len, ++ u8 action, u32 tcp_seq_num, u32 tcp_ack_num); ++ ++/* ++ * An incoming packet handler. ++ * @param pkt pointer to the application packet ++ * @param dport destination UDP port ++ * @param sip source IP address ++ * @param sport source UDP port ++ * @param len packet length ++ */ ++typedef void rxhand_tcp(uchar *pkt, unsigned int dport, ++ struct in_addr sip, unsigned int sport, ++ unsigned int len); ++void tcp_set_tcp_handler(rxhand_tcp *f); ++ ++void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int len); ++ ++/* ++ * An incoming TCP packet handler for the TCP protocol. ++ * There is also a dynamic function pointer for TCP based commands to ++ * receive incoming traffic after the TCP protocol code has done its work. ++ */ ++ ++void rxhand_action(u8 tcp_action, int payload_len, u32 tcp_seq_num, ++ u32 tcp_ack_num, unsigned int pkt_len, ++ union tcp_build_pkt *b); +diff --git a/net/Kconfig b/net/Kconfig +index f2363e5256..77ab683eb8 100644 +--- a/net/Kconfig ++++ b/net/Kconfig +@@ -22,4 +22,10 @@ config NETCONSOLE + Support the 'nc' input/output device for networked console. + See README.NetConsole for details. + ++config TCP ++ bool "TCP stack" ++ help ++ TCP protocol support with SACK for wget. Selecting this will provide ++ the fastest file transfer possible. ++ + endif # if NET +diff --git a/net/Makefile b/net/Makefile +index 07466879f5..237023407f 100644 +--- a/net/Makefile ++++ b/net/Makefile +@@ -24,6 +24,7 @@ obj-$(CONFIG_CMD_RARP) += rarp.o + obj-$(CONFIG_CMD_SNTP) += sntp.o + obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o + obj-$(CONFIG_UDP_FUNCTION_FASTBOOT) += fastboot.o ++obj-$(CONFIG_TCP) += tcp.o + + # Disable this warning as it is triggered by: + # sprintf(buf, index ? "foo%d" : "foo", index) +diff --git a/net/net.c b/net/net.c +index f831c34599..8ac1ff050f 100644 +--- a/net/net.c ++++ b/net/net.c +@@ -108,6 +108,7 @@ + #if defined(CONFIG_CMD_SNTP) + #include "sntp.h" + #endif ++#include + + /** BOOTP EXTENTIONS **/ + +@@ -380,6 +381,9 @@ void net_init(void) + + /* Only need to setup buffer pointers once. */ + first_call = 0; ++#if defined(CONFIG_TCP) ++ tcp_set_tcp_state(TCP_CLOSED); ++#endif + } + + net_init_loop(); +@@ -790,6 +794,16 @@ return net_send_ip_packet(ether, dest, dport, sport, payload_len, + IPPROTO_UDP, 0, 0, 0); + } + ++#if defined(CONFIG_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) +@@ -821,6 +835,15 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, int dport, int sport, + dport, sport, payload_len); + pkt_hdr_size = eth_hdr_size + IP_UDP_HDR_SIZE; + break; ++#if defined(CONFIG_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; + } + +@@ -1229,6 +1252,16 @@ void net_process_received_packet(uchar *in_packet, int len) + if (ip->ip_p == IPPROTO_ICMP) { + receive_icmp(ip, len, src_ip, et); + return; ++#if defined(CONFIG_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; + } +diff --git a/net/tcp.c b/net/tcp.c +new file mode 100644 +index 0000000000..12fa0a72cd +--- /dev/null ++++ b/net/tcp.c +@@ -0,0 +1,700 @@ ++// 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: ++ * ++ * Prequeisites: - own ethernet address ++ * - own IP address ++ * - Server IP address ++ * - Server with TCP ++ * - TCP application (eg wget) ++ * Next Step HTTPS? ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ++ * 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; ++ ++u32 tcp_seq_init; ++u32 tcp_ack_edge; ++u32 tcp_seq_max; ++ ++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; ++}; ++ ++struct sack_r edge_a[TCP_SACK]; ++unsigned int sack_idx; ++unsigned int prev_len; ++ ++/* TCP connection state */ ++static enum TCP_STATE tcp_state; ++ ++/* ++ * An incoming TCP packet handler for the TCP protocol. ++ * There is also a dynamic function pointer for TCP based commands to ++ * receive incoming traffic after the TCP protocol code has done its work. ++ */ ++ ++/* Current TCP RX packet handler */ ++static rxhand_tcp *tcp_packet_handler; ++ ++enum TCP_STATE tcp_get_tcp_state(void) ++{ ++ return tcp_state; ++} ++ ++void tcp_set_tcp_state(enum TCP_STATE new_state) ++{ ++ tcp_state = new_state; ++} ++ ++static void dummy_handler(uchar *pkt, unsigned int dport, ++ struct in_addr sip, unsigned int sport, ++ unsigned int len) ++{ ++} ++ ++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; ++} ++ ++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] = 0x00; ++ ++ net_copy_ip((void *)&b->ph.p_src, &src); ++ net_copy_ip((void *)&b->ph.p_dst, &dest); ++ b->ph.rsvd = 0x00; ++ 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); ++} ++ ++int net_set_ack_options(union tcp_build_pkt *b) ++{ ++ b->sack.hdr.tcp_hlen = (TCP_HDR_SIZE >> 2) << 4; ++ ++ 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 = 0x00; ++ ++ 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; ++ } ++ ++ /* ++ * 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. ++ */ ++ ++ b->sack.hdr.tcp_hlen = (((TCP_HDR_SIZE + TCP_TSOPT_SIZE ++ + tcp_lost.len + 3) >> 2) << 4); ++ ++ /* ++ * This returns the actual rounded up length of the ++ * TCP header to add to the total packet length ++ */ ++ ++ return b->sack.hdr.tcp_hlen >> 2; ++} ++ ++void net_set_syn_options(union tcp_build_pkt *b) ++{ ++ 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; ++ b->ip.sack_p.kind = TCP_P_SACK; ++ b->ip.sack_p.len = TCP_OPT_LEN_2; ++ b->ip.t_opt.kind = TCP_O_TS; ++ b->ip.t_opt.len = TCP_OPT_LEN_A; ++ loc_timestamp = get_ticks(); ++ rmt_timestamp = 0x00000000; ++ b->ip.t_opt.t_snd = 0; ++ b->ip.t_opt.t_rcv = 0x00000000; ++ 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 = 0x50; ++ ++ switch (action) { ++ case TCP_SYN: ++ debug_cond(DEBUG_DEV_PKT, ++ "TCP Hdr:SYN (%pI4, %pI4, sq=%d, ak=%d)\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 (tcp_state == TCP_SYN_SENT) { /* Too many SYNs */ ++ action = TCP_FIN; ++ tcp_state = TCP_FIN_WAIT_1; ++ } else { ++ tcp_state = TCP_SYN_SENT; ++ } ++ break; ++ 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=%d, a=%d, 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=%d, a=%d)\n", ++ &net_server_ip, &net_ip, tcp_seq_num, tcp_ack_num); ++ payload_len = 0; ++ pkt_hdr_len = IP_TCP_HDR_SIZE; ++ tcp_state = TCP_FIN_WAIT_1; ++ ++ break; ++ ++ /* Notify connection closing */ ++ ++ case (TCP_FIN | TCP_ACK): ++ case ((TCP_FIN | TCP_ACK) | TCP_PUSH): ++ if (tcp_state == TCP_CLOSE_WAIT) ++ tcp_state = TCP_CLOSING; ++ tcp_ack_edge++; ++ debug_cond(DEBUG_DEV_PKT, ++ "TCP Hdr:FIN ACK PSH(%pI4, %pI4, s=%d, a=%d, A=%x)\n", ++ &net_server_ip, &net_ip, ++ tcp_seq_num, tcp_ack_edge, action); ++ /* FALLTHRU */ ++ 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=%d, a=%d, 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 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_seq_num = tcp_seq_num + payload_len; ++ ++ /* ++ * 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 = 0x0000; ++ b->ip.hdr.tcp_ugr = 0x0000; ++ ++ 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; ++} ++ ++/* ++ * Selective Acknowledgment (Essential for fast stream transfer) ++ */ ++ ++void tcp_hole(u32 tcp_seq_num, u32 len, u32 tcp_seq_max) ++{ ++ unsigned int idx_sack; ++ unsigned int sack_end = TCP_SACK - 1; ++ unsigned int sack_in; ++ unsigned int 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; ++ ++ 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"); ++ 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++; ++ 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 (hill < TCP_SACK_HILLS) ++ tcp_lost.hill[hill].r = ++ edge_a[sack_in].se.r; ++ if (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 (tcp_lost.len <= TCP_OPT_LEN_2) ++ sack_idx = 0; ++} ++ ++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] != 0) { ++ switch (p[0]) { ++ case TCP_O_END: ++ return; /* Finished processing options */ ++ case TCP_O_MSS: ++ case TCP_O_SCL: ++ case TCP_P_SACK: ++ case TCP_V_SACK: ++ break; /* Continue to process options */ ++ case TCP_O_TS: ++ tsopt = (struct tcp_t_opt *)p; ++ rmt_timestamp = tsopt->t_snd; ++ return; ++ break; ++ } /* End switch, process optional NOPs */ ++ ++ if (p[0] == TCP_O_NOP) ++ p++; ++ } else { ++ return; /* Finished processing options */ ++ } ++ } ++} ++ ++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; ++ tcp_state = TCP_CLOSED; ++ net_set_state(NETLOOP_FAIL); ++ debug_cond(DEBUG_INT_STATE, "TCP Reset %x\n", tcp_flags); ++ return TCP_RST; ++ } ++ ++ switch (tcp_state) { ++ case TCP_CLOSED: ++ debug_cond(DEBUG_INT_STATE, "TCP CLOSED %x\n", tcp_flags); ++ if (tcp_fin) ++ action = TCP_DATA; ++ if (tcp_syn) ++ action = TCP_RST; ++ if (tcp_ack) ++ action = TCP_DATA; ++ break; ++ case TCP_SYN_SENT: ++ debug_cond(DEBUG_INT_STATE, "TCP_SYN_SENT %x, %d\n", ++ tcp_flags, *tcp_seq_num); ++ if (tcp_fin) { ++ action = action | TCP_PUSH; ++ tcp_state = TCP_CLOSE_WAIT; ++ } ++ if (tcp_syn) { ++ action = action | TCP_ACK | TCP_PUSH; ++ if (tcp_ack) { ++ tcp_seq_init = *tcp_seq_num; ++ *tcp_seq_num = *tcp_seq_num + 1; ++ tcp_seq_max = *tcp_seq_num; ++ tcp_ack_edge = *tcp_seq_num; ++ sack_idx = 0; ++ edge_a[sack_idx].se.l = *tcp_seq_num; ++ edge_a[sack_idx].se.r = *tcp_seq_num; ++ prev_len = 0; ++ tcp_state = TCP_ESTABLISHED; ++ for (i = 0; i < TCP_SACK; i++) ++ edge_a[i].st = NOPKT; ++ } ++ } else { ++ if (tcp_ack) ++ action = TCP_DATA; ++ } ++ break; ++ case TCP_ESTABLISHED: ++ debug_cond(DEBUG_INT_STATE, ++ "TCP_ESTABLISHED %x\n", tcp_flags); ++ if (*tcp_seq_num > tcp_seq_max) ++ tcp_seq_max = *tcp_seq_num; ++ if (payload_len > 0) { ++ tcp_hole(*tcp_seq_num, payload_len, tcp_seq_max); ++ tcp_fin = TCP_DATA; /* cause standalone FIN */ ++ } ++ ++ if ((tcp_fin) && tcp_lost.len <= TCP_OPT_LEN_2) { ++ action = action | TCP_FIN | TCP_PUSH | TCP_ACK; ++ tcp_state = TCP_CLOSE_WAIT; ++ } else { ++ if (tcp_ack) ++ action = TCP_DATA; ++ } ++ if (tcp_push) ++ action = action | TCP_PUSH; ++ if (tcp_syn) ++ action = TCP_ACK + TCP_RST; ++ break; ++ case TCP_CLOSE_WAIT: ++ debug_cond(DEBUG_INT_STATE, "TCP_CLOSE_WAIT (%x)\n", tcp_flags); ++ action = TCP_DATA; /* Wait for app */ ++ break; ++ case TCP_FIN_WAIT_2: ++ debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_2 (%x)\n", tcp_flags); ++ if (tcp_fin) ++ action = TCP_DATA; ++ if (tcp_syn) ++ action = TCP_DATA; ++ if (tcp_ack) { ++ action = TCP_PUSH | TCP_ACK; ++ tcp_state = TCP_CLOSED; ++ puts("\n"); ++ } ++ break; ++ case TCP_FIN_WAIT_1: ++ debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_1 (%x)\n", tcp_flags); ++ if (tcp_fin) { ++ action = TCP_ACK | TCP_FIN; ++ tcp_state = TCP_FIN_WAIT_2; ++ } ++ if (tcp_syn) ++ action = TCP_RST; ++ if (tcp_ack) { ++ tcp_state = TCP_CLOSED; ++ tcp_seq_num = tcp_seq_num + 1; ++ } ++ break; ++ case TCP_CLOSING: ++ debug_cond(DEBUG_INT_STATE, "TCP_CLOSING (%x)\n", tcp_flags); ++ if (tcp_fin) ++ action = TCP_DATA; ++ if (tcp_syn) ++ action = TCP_RST; ++ if (tcp_ack) { ++ action = TCP_PUSH; ++ tcp_state = TCP_CLOSED; ++ puts("\n"); ++ } ++ break; ++ } ++ return action; ++} ++ ++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; ++ u32 tcp_ack_num; ++ struct in_addr action_and_state; ++ ++ int tcp_hdr_len; ++ int 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); ++ ++ debug_cond(DEBUG_DEV_PKT, ++ "In__________________________________________\n"); ++ ++ b->ip.hdr.ip_src = net_server_ip; ++ b->ip.hdr.ip_dst = net_ip; ++ b->ip.hdr.ip_sum = 0x0000; ++ 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 = 0x0000; ++ 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 = (b->ip.hdr.tcp_hlen >> 2); ++ 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); ++ ++ /* ++ * State-altering command to be sent. ++ * The packet sequence and ack numbers are in the tcp_seq_num ++ * and tcp_ack_num variables. The current packet, its position ++ * in the data stream, is the in the range of those variables. ++ * ++ * In the "application push" invocation, the TCP header with all ++ * its information is pointed to by the packet pointer. ++ * ++ * In the typedef ++ * void rxhand_tcp(uchar *pkt, unsigned int dport, ++ * struct in_addr sip, unsigned int sport, ++ * unsigned int len); ++ * *pkt is the pointer to the payload ++ * dport is used for tcp_seg_num ++ * action_and_state.s_addr is used for TCP state ++ * sport is used for tcp_ack_num (which is unused by the app) ++ * pkt_ length is the payload length. ++ * ++ * TCP_PUSH from the state machine with a payload length of 0 is a ++ * connect or disconnect event ++ */ ++ ++ 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=%d,Ack=%d,Pay%d)\n", ++ tcp_action, tcp_seq_num, tcp_ack_num, payload_len); ++ ++ action_and_state.s_addr = tcp_action; ++ (*tcp_packet_handler) ((uchar *)b + pkt_len - payload_len, ++ tcp_seq_num, action_and_state, ++ tcp_ack_num, payload_len); ++ ++ } else if (tcp_action != TCP_DATA) { ++ debug_cond(DEBUG_DEV_PKT, ++ "TCP Action (action=%x,Seq=%d,Ack=%d,Pay=%d)\n", ++ tcp_action, tcp_seq_num, tcp_ack_num, 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_seq_num, tcp_ack_num); ++ } ++} + +From patchwork Sun Jun 24 22:40:43 2018 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [U-Boot,v12,3/3] Add wget application +X-Patchwork-Submitter: Duncan Hare +X-Patchwork-Id: 934007 +X-Patchwork-Delegate: joe.hershberger@gmail.com +Message-Id: <20180624224043.31503-3-DH@synoia.com> +To: DuncanCHare@yahoo.com +Cc: u-boot@lists.denx.de, Joe Hershberger , + Jason Kridner , + Maxime Ripard , + Chris Packham +Date: Sun, 24 Jun 2018 15:40:43 -0700 +From: DH@synoia.com +List-Id: U-Boot discussion + +From: Duncan Hare + +Commit-notes +All the code is new, and not copied from any source + +Test wget + +wget is a general purpose TCP/IP program. There is a Linux/unix version +of wget, which enables server verification without running u-boot. + +1. The python distribution include a simple http request handler. +To use it with u-boot and Linux wget start the python server +in the directory which contains your test files. + +sudo python -m SimplleHTTPServer 80 + +sudo becuse the port number used is lower than 1,000. +Leave this window open. + +Then test the server, in a directory different from your directory +containing test file, assuming a test file named "test file.name" +at a Linux command prompt enter: + +wget localhost/testfile.name + +and the file should be transferred to the receiving directory. + +The server verification is now complete. + +2. wget in u-boot needs a load address, server ip address, an ip +address, optional file path, and file name. + +The server and u-boot ip address can be 127.0.0.1. The routing is then +between the u-boot test environment and the server in the same machine. + +u-boot script example + +env set ipaddr 127.0.0.1; env set loadaddr 0x8000000; +wget 127.0.0.1 testfile.name + +or + +env set ipaddr 127.0.0.1; env set loadaddr 0x8000000; +wget 127.0.0.1 pathname/testfile.name + +wget prints a progress "|" every 127 packets, and at the end of transfer +prints a message of success or fail, and bytes transferred. + +When testing u-boot, we first did a large file transfer with wget +and then modified tftp to download the same file and verify wget's +transfer was both complete and correct, with a strcmp function replacing +the copy to kernel area code fragment. +END + +Cover-letter +Add TCP and WGET +This is a limited implementation of a TCP, a receiver, with +selective acknowledgment (SACK), and wget as a transfer protocol over TCP. + +TCP with SACK provides maximum transfer rates over fast and fast multi-hop +networks. TCP continues stream transfer if packets are dropped, and then +recoveres dropped packed asynchronously. + +WGET is a very efficient file transfer protocol running over TCP. +There is a single request for each file transferred, irrespective of file size. +END + +Signed-off-by: Duncan Hare +--- + + cmd/Kconfig | 7 + + cmd/net.c | 13 ++ + include/net.h | 2 +- + include/net/wget.h | 19 +++ + net/Makefile | 2 +- + net/net.c | 9 +- + net/wget.c | 426 +++++++++++++++++++++++++++++++++++++++++++++++++++++ + 7 files changed, 475 insertions(+), 3 deletions(-) + create mode 100644 include/net/wget.h + create mode 100644 net/wget.c + +diff --git a/cmd/Kconfig b/cmd/Kconfig +index 45c83359ad..fb9c894363 100644 +--- a/cmd/Kconfig ++++ b/cmd/Kconfig +@@ -1192,6 +1192,13 @@ config CMD_NFS + help + Boot image via network using NFS protocol. + ++config CMD_WGET ++ bool "wget" ++ select TCP ++ help ++ Download kernel, or other files, from a web server over TCP. ++ Fast file transfer over networks with latenc ++ + config CMD_MII + bool "mii" + help +diff --git a/cmd/net.c b/cmd/net.c +index f83839c35e..f5fde849c4 100644 +--- a/cmd/net.c ++++ b/cmd/net.c +@@ -113,6 +113,19 @@ U_BOOT_CMD( + ); + #endif + ++#if defined(CONFIG_CMD_WGET) ++static int do_wget(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) ++{ ++ return netboot_common(WGET, cmdtp, argc, argv); ++} ++ ++U_BOOT_CMD( ++ wget, 3, 1, do_wget, ++ "boot image via network using HTTP protocol", ++ "[loadAddress] [[hostIPaddr:]path and image name]" ++); ++#endif ++ + static void netboot_update_env(void) + { + char tmp[22]; +diff --git a/include/net.h b/include/net.h +index ba96267eb8..14b013083d 100644 +--- a/include/net.h ++++ b/include/net.h +@@ -539,7 +539,7 @@ extern int net_restart_wrap; /* Tried all network devices */ + + enum proto_t { + BOOTP, RARP, ARP, TFTPGET, DHCP, PING, DNS, NFS, CDP, NETCONS, SNTP, +- TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT ++ TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WGET + }; + + extern char net_boot_file_name[1024];/* Boot File name */ +diff --git a/include/net/wget.h b/include/net/wget.h +new file mode 100644 +index 0000000000..61bdd851f9 +--- /dev/null ++++ b/include/net/wget.h +@@ -0,0 +1,19 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Duncan Hare Copyright 2017 ++ */ ++ ++void wget_start(void); /* Begin wget */ ++ ++enum WGET_STATE { ++ WGET_CLOSED, ++ WGET_CONNECTING, ++ WGET_CONNECTED, ++ WGET_TRANSFERRING, ++ WGET_TRANSFERRED ++}; ++ ++#define DEBUG_WGET 0 /* Set to 1 for debug messges */ ++#define SERVER_PORT 80 ++#define WGET_RETRY_COUNT 30 ++#define WGET_TIMEOUT 2000UL +diff --git a/net/Makefile b/net/Makefile +index 237023407f..882af145aa 100644 +--- a/net/Makefile ++++ b/net/Makefile +@@ -23,8 +23,8 @@ obj-$(CONFIG_CMD_PING) += ping.o + obj-$(CONFIG_CMD_RARP) += rarp.o + obj-$(CONFIG_CMD_SNTP) += sntp.o + obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o +-obj-$(CONFIG_UDP_FUNCTION_FASTBOOT) += fastboot.o + obj-$(CONFIG_TCP) += tcp.o ++obj-$(CONFIG_CMD_WGET) += wget.o + + # Disable this warning as it is triggered by: + # sprintf(buf, index ? "foo%d" : "foo", index) +diff --git a/net/net.c b/net/net.c +index 8ac1ff050f..39707d5b18 100644 +--- a/net/net.c ++++ b/net/net.c +@@ -109,6 +109,7 @@ + #include "sntp.h" + #endif + #include ++#include + + /** BOOTP EXTENTIONS **/ + +@@ -494,6 +495,11 @@ restart: + nfs_start(); + break; + #endif ++#if defined(CONFIG_CMD_WGET) ++ case WGET: ++ wget_start(); ++ break; ++#endif + #if defined(CONFIG_CMD_CDP) + case CDP: + cdp_start(); +@@ -1540,7 +1546,8 @@ void copy_filename(char *dst, const char *src, int size) + + #if defined(CONFIG_CMD_NFS) || \ + defined(CONFIG_CMD_SNTP) || \ +- defined(CONFIG_CMD_DNS) ++ defined(CONFIG_CMD_DNS) || \ ++ defined(CONFIG_CMD_WGET) + /* + * make port a little random (1024-17407) + * This keeps the math somewhat trivial to compute, and seems to work with +diff --git a/net/wget.c b/net/wget.c +new file mode 100644 +index 0000000000..8238ec52c4 +--- /dev/null ++++ b/net/wget.c +@@ -0,0 +1,426 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * WGET/HTTP support driver based on U-BOOT's nfs.c ++ * Copyright Duncan Hare 2017 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++const char bootfile1[] = "GET "; ++const char bootfile3[] = " HTTP/1.0\r\n\r\n"; ++const char http_eom[] = "\r\n\r\n"; ++const char http_ok[] = "200"; ++const char content_len[] = "Content-Length"; ++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. ++ */ ++ ++static struct pkt_qd pkt_q[PKTBUFSRX / 4]; ++static int pkt_q_idx; ++static unsigned long content_length; ++static unsigned int packets; ++ ++static unsigned int initial_data_seq_num; ++ ++static enum WGET_STATE wget_state; ++ ++static char *image_url; ++static unsigned int wget_timeout = WGET_TIMEOUT; ++ ++static void wget_timeout_handler(void); ++ ++static enum net_loop_state wget_loop_state; ++ ++/* Timeout retry parameters */ ++static u8 retry_action; ++static unsigned int retry_tcp_ack_num; ++static unsigned int retry_tcp_seq_num; ++static int retry_len; ++ ++static inline int store_block(uchar *src, unsigned int offset, unsigned int len) ++{ ++ ulong newsize = offset + len; ++ uchar *ptr; ++ ++#ifdef CONFIG_SYS_DIRECT_FLASH_WGET ++ int i, rc = 0; ++ ++ for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) { ++ /* start address in flash? */ ++ if (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)(load_addr + offset), len); ++ if (rc) { ++ flash_perror(rc); ++ return -1; ++ } ++ } else { ++#endif /* CONFIG_SYS_DIRECT_FLASH_WGET */ ++ ++ ptr = map_sysmem(load_addr + offset, len); ++ memcpy(ptr, src, len); ++ unmap_sysmem(ptr); ++ ++#ifdef CONFIG_SYS_DIRECT_FLASH_WGET ++ } ++#endif ++ if (net_boot_file_size < (offset + len)) ++ net_boot_file_size = newsize; ++ return 0; ++} ++ ++/* ++ * 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; ++ unsigned int tcp_ack_num = retry_tcp_ack_num; ++ unsigned int tcp_seq_num = retry_tcp_seq_num; ++ int len = retry_len; ++ uchar *ptr; ++ uchar *offset; ++ ++ tcp_ack_num = tcp_ack_num + len; ++ ++ switch (wget_state) { ++ case WGET_CLOSED: ++ debug_cond(DEBUG_WGET, "wget: send SYN\n"); ++ 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 = offset + strlen(bootfile1); ++ ++ memcpy(offset, image_url, strlen(image_url)); ++ offset = offset + strlen(image_url); ++ ++ memcpy(offset, &bootfile3, strlen(bootfile3)); ++ offset = offset + strlen(bootfile3); ++ net_send_tcp_packet((offset - ptr), SERVER_PORT, our_port, ++ TCP_PUSH, tcp_seq_num, tcp_ack_num); ++ 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_ack_num, ++ unsigned int tcp_seq_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("%s", error_message); ++ printf("%s", "wget: Transfer Fail\n"); ++ net_set_timeout_handler(0, NULL); ++ wget_send(action, tcp_seq_num, tcp_ack_num, 0); ++} ++ ++void wget_success(u8 action, unsigned int tcp_seq_num, ++ unsigned int tcp_ack_num, int len, int packets) ++{ ++ printf("Packets received %d, Transfer Successful\n", packets); ++ wget_send(action, tcp_seq_num, tcp_ack_num, len); ++} ++ ++/* ++ * 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(); ++ } ++} ++ ++static void wget_connected(uchar *pkt, unsigned int tcp_seq_num, ++ struct in_addr action_and_state, ++ unsigned int tcp_ack_num, unsigned int len) ++{ ++ u8 action = action_and_state.s_addr; ++ uchar *pkt_in_q; ++ char *pos; ++ int hlen; ++ int i; ++ ++ pkt[len] = '\0'; ++ pos = strstr((char *)pkt, http_eom); ++ ++ if (pos == 0) { ++ debug_cond(DEBUG_WGET, ++ "wget: Connected, data before Header %p\n", pkt); ++ pkt_in_q = (void *)load_addr + 0x20000 + (pkt_q_idx * 0x800); ++ memcpy(pkt_in_q, pkt, len); ++ 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++; ++ } else { ++ debug_cond(DEBUG_WGET, "wget: Connected HTTP Header %p\n", pkt); ++ 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); ++ ++ wget_state = WGET_TRANSFERRING; ++ ++ 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: Connctd pkt %p hlen %x\n", ++ pkt, hlen); ++ initial_data_seq_num = tcp_seq_num + hlen; ++ ++ pos = strstr((char *)pkt, content_len); ++ if (!pos) { ++ content_length = -1; ++ } else { ++ pos = 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) ++ store_block(pkt + hlen, 0, len - hlen); ++ debug_cond(DEBUG_WGET, ++ "wget: Connected Pkt %p hlen %x\n", ++ pkt, hlen); ++ ++ for (i = 0; i < pkt_q_idx; i++) { ++ store_block(pkt_q[i].pkt, ++ pkt_q[i].tcp_seq_num - ++ initial_data_seq_num, ++ pkt_q[i].len); ++ debug_cond(DEBUG_WGET, ++ "wget: Connctd pkt Q %p len %x\n", ++ pkt_q[i].pkt, pkt_q[i].len); ++ } ++ } ++ } ++ wget_send(action, tcp_seq_num, tcp_ack_num, len); ++} ++ ++ /* ++ * In the "application push" invocation, the TCP header with all ++ * its information is pointed to by the packet pointer. ++ * ++ * in the typedef ++ * void rxhand_tcp(uchar *pkt, unsigned int dport, ++ * struct in_addr sip, unsigned int sport, ++ * unsigned int len); ++ * *pkt is the pointer to the payload ++ * dport is used for tcp_seg_num ++ * action_and_state.s_addr is used for TCP state ++ * sport is used for tcp_ack_num (which is unused by the app) ++ * pkt_ length is the payload length. ++ */ ++static void wget_handler(uchar *pkt, unsigned int tcp_seq_num, ++ struct in_addr action_and_state, ++ unsigned int tcp_ack_num, unsigned int len) ++{ ++ enum TCP_STATE wget_tcp_state = tcp_get_tcp_state(); ++ u8 action = action_and_state.s_addr; ++ ++ net_set_timeout_handler(wget_timeout, wget_timeout_handler); ++ packets++; ++ ++ switch (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=%x, Ack=%x\n", ++ len, tcp_seq_num, tcp_ack_num); ++ if (len == 0) { ++ 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=%x, len=%x\n", ++ tcp_seq_num, len); ++ if (len == 0) { ++ wget_fail("Image not found, no data returned\n", ++ tcp_seq_num, tcp_ack_num, action); ++ } else { ++ wget_connected(pkt, tcp_seq_num, action_and_state, ++ 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 (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); ++ return; ++ } ++ ++ switch (wget_tcp_state) { ++ case TCP_FIN_WAIT_2: ++ wget_send(TCP_ACK, tcp_seq_num, tcp_ack_num, len); ++ case TCP_SYN_SENT: ++ 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 */ ++ 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); ++ break; ++ } ++} ++ ++void wget_start(void) ++{ ++ debug_cond(DEBUG_WGET, "%s\n", __func__); ++ ++ image_url = strchr(net_boot_file_name, ':'); ++ if (image_url > 0) { ++ web_server_ip = string_to_ip(net_boot_file_name); ++ ++image_url; ++ } 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 << 9); ++ print_size(net_boot_file_expected_size_in_blocks << 9, ""); ++ } ++ debug_cond(DEBUG_WGET, ++ "\nwget:Load address: 0x%lx\nLoading: *\b", load_addr); ++ ++ net_set_timeout_handler(wget_timeout, wget_timeout_handler); ++ tcp_set_tcp_handler(wget_handler); ++ ++ wget_timeout_count = 0; ++ wget_state = WGET_CLOSED; ++ ++ our_port = random_port(); ++ ++ /* ++ * Zero out server ether to forece apr resolution in case ++ * the server ip for the previous u-boot commsnd, for exanple dns ++ * is not the same as the web server ip. ++ */ ++ ++ memset(net_server_ethaddr, 0, 6); ++ ++ wget_send(TCP_SYN, 0, 0, 0); ++} diff --git a/u-boot-patches/U-Boot-v12-2-3-Add-TCP.diff b/u-boot-patches/U-Boot-v12-2-3-Add-TCP.diff new file mode 100644 index 0000000..59a8704 --- /dev/null +++ b/u-boot-patches/U-Boot-v12-2-3-Add-TCP.diff @@ -0,0 +1,1085 @@ +diff --git a/include/net.h b/include/net.h +index a54160fff6..ba96267eb8 100644 +--- a/include/net.h ++++ b/include/net.h +@@ -26,6 +26,9 @@ + * + */ + ++#if defined(CONFIG_TCP) /* Protected UDP uses less bufferes than TCP */ ++#define CONFIG_SYS_RX_ETH_BUFFER 12 ++#endif + #ifdef CONFIG_SYS_RX_ETH_BUFFER + # define PKTBUFSRX CONFIG_SYS_RX_ETH_BUFFER + #else +@@ -350,6 +353,7 @@ struct vlan_ethernet_hdr { + #define PROT_PPP_SES 0x8864 /* PPPoE session messages */ + + #define IPPROTO_ICMP 1 /* Internet Control Message Protocol */ ++#define IPPROTO_TCP 6 /* Transmission Control Protocol */ + #define IPPROTO_UDP 17 /* User Datagram Protocol */ + + /* +@@ -659,7 +663,7 @@ static inline void net_send_packet(uchar *pkt, int len) + } + + /* +- * Transmit "net_tx_packet" as UDP packet, performing ARP request if needed ++ * Transmit "net_tx_packet" as UDP or TCPpacket, send ARP request if needed + * (ether will be populated) + * + * @param ether Raw packet buffer +@@ -667,10 +671,15 @@ static inline void net_send_packet(uchar *pkt, int len) + * @param dport Destination UDP port + * @param sport Source UDP port + * @param payload_len Length of data after the UDP header ++ * @param action TCP action to be performed ++ * @param tcp_seq_num TCP sequence number of this transmission ++ * @param tcp_ack_num TCP stream acknolegement number + */ + 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); ++int net_send_tcp_packet(int payload_len, int dport, int sport, u8 action, ++ u32 tcp_seq_num, u32 tcp_ack_num); + int net_send_udp_packet(uchar *ether, struct in_addr dest, int dport, + int sport, int payload_len); + +diff --git a/include/net/tcp.h b/include/net/tcp.h +new file mode 100644 +index 0000000000..d0e90e07dd +--- /dev/null ++++ b/include/net/tcp.h +@@ -0,0 +1,227 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * TCP Support with SACK for file transfer. ++ * ++ * Copyright 2017 Duncan Hare, All rights reserved. ++ */ ++ ++#define TCP_ACTIVITY 127 /* Number of packets received */ ++ /* before console progress mark */ ++ ++struct ip_tcp_hdr { ++ u8 ip_hl_v; /* header length and version */ ++ u8 ip_tos; /* type of service */ ++ u16 ip_len; /* total length */ ++ u16 ip_id; /* identification */ ++ u16 ip_off; /* fragment offset field */ ++ u8 ip_ttl; /* time to live */ ++ u8 ip_p; /* protocol */ ++ u16 ip_sum; /* checksum */ ++ struct in_addr ip_src; /* Source IP address */ ++ struct in_addr ip_dst; /* Destination IP address */ ++ u16 tcp_src; /* TCP source port */ ++ u16 tcp_dst; /* TCP destination port */ ++ u32 tcp_seq; /* TCP sequence number */ ++ u32 tcp_ack; /* TCP Acknowledgment number */ ++ u8 tcp_hlen; /* 4 bits TCP header Length/4 */ ++ /* 4 bits Reserved */ ++ /* 2 more bits reserved */ ++ u8 tcp_flags; /* see defines */ ++ u16 tcp_win; /* TCP windows size */ ++ u16 tcp_xsum; /* Checksum */ ++ u16 tcp_ugr; /* Pointer to urgent data */ ++} __packed; ++ ++#define IP_TCP_HDR_SIZE (sizeof(struct ip_tcp_hdr)) ++#define TCP_HDR_SIZE (IP_TCP_HDR_SIZE - IP_HDR_SIZE) ++ ++#define TCP_DATA 0x00 /* Data Packet - internal use only */ ++#define TCP_FIN 0x01 /* Finish flag */ ++#define TCP_SYN 0x02 /* Synch (start) flag */ ++#define TCP_RST 0x04 /* reset flag */ ++#define TCP_PUSH 0x08 /* Push - Notify app */ ++#define TCP_ACK 0x10 /* Acknowledgment of data received */ ++#define TCP_URG 0x20 /* Urgent */ ++#define TCP_ECE 0x40 /* Congestion control */ ++#define TCP_CWR 0x80 /* Congestion Control */ ++ ++/* ++ * TCP header options, Seq, MSS, and SACK ++ */ ++ ++#define TCP_SACK 32 /* Number of packets analyzed */ ++ /* on leading edge of stream */ ++ ++#define TCP_O_END 0x00 /* End of option list */ ++#define TCP_1_NOP 0x01 /* Single padding NOP */ ++#define TCP_O_NOP 0x01010101 /* NOPs pad to 32 bit boundary */ ++#define TCP_O_MSS 0x02 /* MSS Size option */ ++#define TCP_O_SCL 0x03 /* Window Scale option */ ++#define TCP_P_SACK 0x04 /* SACK permitted */ ++#define TCP_V_SACK 0x05 /* SACK values */ ++#define TCP_O_TS 0x08 /* Timestamp option */ ++#define TCP_OPT_LEN_2 0x02 ++#define TCP_OPT_LEN_3 0x03 ++#define TCP_OPT_LEN_4 0x04 ++#define TCP_OPT_LEN_6 0x06 ++#define TCP_OPT_LEN_8 0x08 ++#define TCP_OPT_LEN_A 0x0a /* Timestamp Length */ ++ ++/* ++ * Please review the warning in net.c about these two parameters. ++ * They are part of a promise of RX buffer size to the sending TCP ++ */ ++ ++#define TCP_MSS 1460 /* Max segment size */ ++#define TCP_SCALE 0x01 /* Scale */ ++ ++struct tcp_mss { /* TCP Max Segment size */ ++ u8 kind; /* Field ID */ ++ u8 len; /* Field length */ ++ u16 mss; /* Segment size value */ ++} __packed; ++ ++struct tcp_scale { /* TCP Windows Scale */ ++ u8 kind; /* Field ID */ ++ u8 len; /* Filed length */ ++ u8 scale; /* windows shift value used for */ ++ /* networks with many hops */ ++ /* Typically 4 or more hops */ ++ ++} __packed; ++ ++struct tcp_sack_p { /* SACK permitted */ ++ u8 kind; /* Field Id */ ++ u8 len; /* Field length */ ++} __packed; ++ ++ /* Terse definitions used */ ++ /* long definitions make the */ ++ /* indented code overflow line */ ++ /* length linits */ ++struct sack_edges { ++ u32 l; /* Left edge of stream */ ++ u32 r; /* right edge of stream */ ++} __packed; ++ ++#define TCP_SACK_SIZE (sizeof(struct sack_edges)) ++ ++/* ++ * A TCP stream has holes when packets are missing or disordered. ++ * A hill is the inverese of a hole, and is data received. ++ * TCP receiveds hills (a sequence of data), and inferrs Holes ++ * from the "hills" or packets received. ++ */ ++ ++#define TCP_SACK_HILLS 4 ++ ++struct tcp_sack_v { ++ u8 kind; /* Field ID */ ++ u8 len; /* Field Length */ ++ struct sack_edges hill[TCP_SACK_HILLS]; /* L & R window edges */ ++} __packed; ++ ++struct tcp_t_opt { /* TCP time stamps option */ ++ u8 kind; /* Field id */ ++ u8 len; /* Field length */ ++ u32 t_snd; /* Sender timestamp */ ++ u32 t_rcv; /* Receiver timestamp */ ++} __packed; ++ ++#define TCP_TSOPT_SIZE (sizeof(struct tcp_t_opt)) ++ ++/* ++ * ip tcp structure with options ++ */ ++ ++struct ip_tcp_hdr_o { ++ struct ip_tcp_hdr hdr; ++ struct tcp_mss mss; ++ struct tcp_scale scale; ++ struct tcp_sack_p sack_p; ++ struct tcp_t_opt t_opt; ++ u8 end; ++} __packed; ++ ++#define IP_TCP_O_SIZE (sizeof(struct ip_tcp_hdr_o)) ++ ++struct ip_tcp_hdr_s { ++ struct ip_tcp_hdr hdr; ++ struct tcp_t_opt t_opt; ++ struct tcp_sack_v sack_v; ++ u8 end; ++} __packed; ++ ++#define IP_TCP_SACK_SIZE (sizeof(struct ip_tcp_hdr_s)) ++ ++/* ++ * TCP pseudo header definitions ++ */ ++#define PSEUDO_PAD_SIZE 8 ++ ++struct pseudo_hdr { ++ u8 padding[PSEUDO_PAD_SIZE]; /* pseudo hdr size = ip_tcp hdr size */ ++ struct in_addr p_src; ++ struct in_addr p_dst; ++ u8 rsvd; ++ u8 p; ++ u16 len; ++} __packed; ++ ++#define PSEUDO_HDR_SIZE (sizeof(struct pseudo_hdr)) - PSEUDO_PAD_SIZE ++ ++/* ++ * union for building TCP/IP packet. Build Pseudo header in packed buffer ++ * first, calculate TCP checksum, then build IP header in packed buffer. ++ */ ++ ++union tcp_build_pkt { ++ struct pseudo_hdr ph; ++ struct ip_tcp_hdr_o ip; ++ struct ip_tcp_hdr_s sack; ++ uchar raw[1600]; ++} __packed; ++ ++/* ++ * TCP State machine states for connection ++ */ ++ ++enum TCP_STATE { ++ TCP_CLOSED, /* Need to send SYN to connect */ ++ TCP_SYN_SENT, /* Trying to connect, waiting for SYN ACK */ ++ TCP_ESTABLISHED, /* both server & client have a connection */ ++ TCP_CLOSE_WAIT, /* Rec FIN, passed to app for FIN, ACK rsp*/ ++ TCP_CLOSING, /* Rec FIN, sent FIN, ACK waiting for ACK */ ++ TCP_FIN_WAIT_1, /* Sent FIN waiting for response */ ++ TCP_FIN_WAIT_2 /* Rec ACK from FIN sent, waiting for FIN */ ++}; ++ ++enum TCP_STATE tcp_get_tcp_state(void); ++void tcp_set_tcp_state(enum TCP_STATE new_state); ++int tcp_set_tcp_header(uchar *pkt, int dport, int sport, int payload_len, ++ u8 action, u32 tcp_seq_num, u32 tcp_ack_num); ++ ++/* ++ * An incoming packet handler. ++ * @param pkt pointer to the application packet ++ * @param dport destination UDP port ++ * @param sip source IP address ++ * @param sport source UDP port ++ * @param len packet length ++ */ ++typedef void rxhand_tcp(uchar *pkt, unsigned int dport, ++ struct in_addr sip, unsigned int sport, ++ unsigned int len); ++void tcp_set_tcp_handler(rxhand_tcp *f); ++ ++void rxhand_tcp_f(union tcp_build_pkt *b, unsigned int len); ++ ++/* ++ * An incoming TCP packet handler for the TCP protocol. ++ * There is also a dynamic function pointer for TCP based commands to ++ * receive incoming traffic after the TCP protocol code has done its work. ++ */ ++ ++void rxhand_action(u8 tcp_action, int payload_len, u32 tcp_seq_num, ++ u32 tcp_ack_num, unsigned int pkt_len, ++ union tcp_build_pkt *b); +diff --git a/net/Kconfig b/net/Kconfig +index f2363e5256..77ab683eb8 100644 +--- a/net/Kconfig ++++ b/net/Kconfig +@@ -22,4 +22,10 @@ config NETCONSOLE + Support the 'nc' input/output device for networked console. + See README.NetConsole for details. + ++config TCP ++ bool "TCP stack" ++ help ++ TCP protocol support with SACK for wget. Selecting this will provide ++ the fastest file transfer possible. ++ + endif # if NET +diff --git a/net/Makefile b/net/Makefile +index 07466879f5..237023407f 100644 +--- a/net/Makefile ++++ b/net/Makefile +@@ -24,6 +24,7 @@ obj-$(CONFIG_CMD_RARP) += rarp.o + obj-$(CONFIG_CMD_SNTP) += sntp.o + obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o + obj-$(CONFIG_UDP_FUNCTION_FASTBOOT) += fastboot.o ++obj-$(CONFIG_TCP) += tcp.o + + # Disable this warning as it is triggered by: + # sprintf(buf, index ? "foo%d" : "foo", index) +diff --git a/net/net.c b/net/net.c +index f831c34599..8ac1ff050f 100644 +--- a/net/net.c ++++ b/net/net.c +@@ -108,6 +108,7 @@ + #if defined(CONFIG_CMD_SNTP) + #include "sntp.h" + #endif ++#include + + /** BOOTP EXTENTIONS **/ + +@@ -380,6 +381,9 @@ void net_init(void) + + /* Only need to setup buffer pointers once. */ + first_call = 0; ++#if defined(CONFIG_TCP) ++ tcp_set_tcp_state(TCP_CLOSED); ++#endif + } + + net_init_loop(); +@@ -790,6 +794,16 @@ return net_send_ip_packet(ether, dest, dport, sport, payload_len, + IPPROTO_UDP, 0, 0, 0); + } + ++#if defined(CONFIG_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) +@@ -821,6 +835,15 @@ int net_send_ip_packet(uchar *ether, struct in_addr dest, int dport, int sport, + dport, sport, payload_len); + pkt_hdr_size = eth_hdr_size + IP_UDP_HDR_SIZE; + break; ++#if defined(CONFIG_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; + } + +@@ -1229,6 +1252,16 @@ void net_process_received_packet(uchar *in_packet, int len) + if (ip->ip_p == IPPROTO_ICMP) { + receive_icmp(ip, len, src_ip, et); + return; ++#if defined(CONFIG_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; + } +diff --git a/net/tcp.c b/net/tcp.c +new file mode 100644 +index 0000000000..12fa0a72cd +--- /dev/null ++++ b/net/tcp.c +@@ -0,0 +1,700 @@ ++// 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: ++ * ++ * Prequeisites: - own ethernet address ++ * - own IP address ++ * - Server IP address ++ * - Server with TCP ++ * - TCP application (eg wget) ++ * Next Step HTTPS? ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ++ * 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; ++ ++u32 tcp_seq_init; ++u32 tcp_ack_edge; ++u32 tcp_seq_max; ++ ++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; ++}; ++ ++struct sack_r edge_a[TCP_SACK]; ++unsigned int sack_idx; ++unsigned int prev_len; ++ ++/* TCP connection state */ ++static enum TCP_STATE tcp_state; ++ ++/* ++ * An incoming TCP packet handler for the TCP protocol. ++ * There is also a dynamic function pointer for TCP based commands to ++ * receive incoming traffic after the TCP protocol code has done its work. ++ */ ++ ++/* Current TCP RX packet handler */ ++static rxhand_tcp *tcp_packet_handler; ++ ++enum TCP_STATE tcp_get_tcp_state(void) ++{ ++ return tcp_state; ++} ++ ++void tcp_set_tcp_state(enum TCP_STATE new_state) ++{ ++ tcp_state = new_state; ++} ++ ++static void dummy_handler(uchar *pkt, unsigned int dport, ++ struct in_addr sip, unsigned int sport, ++ unsigned int len) ++{ ++} ++ ++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; ++} ++ ++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] = 0x00; ++ ++ net_copy_ip((void *)&b->ph.p_src, &src); ++ net_copy_ip((void *)&b->ph.p_dst, &dest); ++ b->ph.rsvd = 0x00; ++ 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); ++} ++ ++int net_set_ack_options(union tcp_build_pkt *b) ++{ ++ b->sack.hdr.tcp_hlen = (TCP_HDR_SIZE >> 2) << 4; ++ ++ 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 = 0x00; ++ ++ 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; ++ } ++ ++ /* ++ * 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. ++ */ ++ ++ b->sack.hdr.tcp_hlen = (((TCP_HDR_SIZE + TCP_TSOPT_SIZE ++ + tcp_lost.len + 3) >> 2) << 4); ++ ++ /* ++ * This returns the actual rounded up length of the ++ * TCP header to add to the total packet length ++ */ ++ ++ return b->sack.hdr.tcp_hlen >> 2; ++} ++ ++void net_set_syn_options(union tcp_build_pkt *b) ++{ ++ 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; ++ b->ip.sack_p.kind = TCP_P_SACK; ++ b->ip.sack_p.len = TCP_OPT_LEN_2; ++ b->ip.t_opt.kind = TCP_O_TS; ++ b->ip.t_opt.len = TCP_OPT_LEN_A; ++ loc_timestamp = get_ticks(); ++ rmt_timestamp = 0x00000000; ++ b->ip.t_opt.t_snd = 0; ++ b->ip.t_opt.t_rcv = 0x00000000; ++ 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 = 0x50; ++ ++ switch (action) { ++ case TCP_SYN: ++ debug_cond(DEBUG_DEV_PKT, ++ "TCP Hdr:SYN (%pI4, %pI4, sq=%d, ak=%d)\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 (tcp_state == TCP_SYN_SENT) { /* Too many SYNs */ ++ action = TCP_FIN; ++ tcp_state = TCP_FIN_WAIT_1; ++ } else { ++ tcp_state = TCP_SYN_SENT; ++ } ++ break; ++ 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=%d, a=%d, 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=%d, a=%d)\n", ++ &net_server_ip, &net_ip, tcp_seq_num, tcp_ack_num); ++ payload_len = 0; ++ pkt_hdr_len = IP_TCP_HDR_SIZE; ++ tcp_state = TCP_FIN_WAIT_1; ++ ++ break; ++ ++ /* Notify connection closing */ ++ ++ case (TCP_FIN | TCP_ACK): ++ case ((TCP_FIN | TCP_ACK) | TCP_PUSH): ++ if (tcp_state == TCP_CLOSE_WAIT) ++ tcp_state = TCP_CLOSING; ++ tcp_ack_edge++; ++ debug_cond(DEBUG_DEV_PKT, ++ "TCP Hdr:FIN ACK PSH(%pI4, %pI4, s=%d, a=%d, A=%x)\n", ++ &net_server_ip, &net_ip, ++ tcp_seq_num, tcp_ack_edge, action); ++ /* FALLTHRU */ ++ 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=%d, a=%d, 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 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_seq_num = tcp_seq_num + payload_len; ++ ++ /* ++ * 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 = 0x0000; ++ b->ip.hdr.tcp_ugr = 0x0000; ++ ++ 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; ++} ++ ++/* ++ * Selective Acknowledgment (Essential for fast stream transfer) ++ */ ++ ++void tcp_hole(u32 tcp_seq_num, u32 len, u32 tcp_seq_max) ++{ ++ unsigned int idx_sack; ++ unsigned int sack_end = TCP_SACK - 1; ++ unsigned int sack_in; ++ unsigned int 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; ++ ++ 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"); ++ 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++; ++ 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 (hill < TCP_SACK_HILLS) ++ tcp_lost.hill[hill].r = ++ edge_a[sack_in].se.r; ++ if (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 (tcp_lost.len <= TCP_OPT_LEN_2) ++ sack_idx = 0; ++} ++ ++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] != 0) { ++ switch (p[0]) { ++ case TCP_O_END: ++ return; /* Finished processing options */ ++ case TCP_O_MSS: ++ case TCP_O_SCL: ++ case TCP_P_SACK: ++ case TCP_V_SACK: ++ break; /* Continue to process options */ ++ case TCP_O_TS: ++ tsopt = (struct tcp_t_opt *)p; ++ rmt_timestamp = tsopt->t_snd; ++ return; ++ break; ++ } /* End switch, process optional NOPs */ ++ ++ if (p[0] == TCP_O_NOP) ++ p++; ++ } else { ++ return; /* Finished processing options */ ++ } ++ } ++} ++ ++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; ++ tcp_state = TCP_CLOSED; ++ net_set_state(NETLOOP_FAIL); ++ debug_cond(DEBUG_INT_STATE, "TCP Reset %x\n", tcp_flags); ++ return TCP_RST; ++ } ++ ++ switch (tcp_state) { ++ case TCP_CLOSED: ++ debug_cond(DEBUG_INT_STATE, "TCP CLOSED %x\n", tcp_flags); ++ if (tcp_fin) ++ action = TCP_DATA; ++ if (tcp_syn) ++ action = TCP_RST; ++ if (tcp_ack) ++ action = TCP_DATA; ++ break; ++ case TCP_SYN_SENT: ++ debug_cond(DEBUG_INT_STATE, "TCP_SYN_SENT %x, %d\n", ++ tcp_flags, *tcp_seq_num); ++ if (tcp_fin) { ++ action = action | TCP_PUSH; ++ tcp_state = TCP_CLOSE_WAIT; ++ } ++ if (tcp_syn) { ++ action = action | TCP_ACK | TCP_PUSH; ++ if (tcp_ack) { ++ tcp_seq_init = *tcp_seq_num; ++ *tcp_seq_num = *tcp_seq_num + 1; ++ tcp_seq_max = *tcp_seq_num; ++ tcp_ack_edge = *tcp_seq_num; ++ sack_idx = 0; ++ edge_a[sack_idx].se.l = *tcp_seq_num; ++ edge_a[sack_idx].se.r = *tcp_seq_num; ++ prev_len = 0; ++ tcp_state = TCP_ESTABLISHED; ++ for (i = 0; i < TCP_SACK; i++) ++ edge_a[i].st = NOPKT; ++ } ++ } else { ++ if (tcp_ack) ++ action = TCP_DATA; ++ } ++ break; ++ case TCP_ESTABLISHED: ++ debug_cond(DEBUG_INT_STATE, ++ "TCP_ESTABLISHED %x\n", tcp_flags); ++ if (*tcp_seq_num > tcp_seq_max) ++ tcp_seq_max = *tcp_seq_num; ++ if (payload_len > 0) { ++ tcp_hole(*tcp_seq_num, payload_len, tcp_seq_max); ++ tcp_fin = TCP_DATA; /* cause standalone FIN */ ++ } ++ ++ if ((tcp_fin) && tcp_lost.len <= TCP_OPT_LEN_2) { ++ action = action | TCP_FIN | TCP_PUSH | TCP_ACK; ++ tcp_state = TCP_CLOSE_WAIT; ++ } else { ++ if (tcp_ack) ++ action = TCP_DATA; ++ } ++ if (tcp_push) ++ action = action | TCP_PUSH; ++ if (tcp_syn) ++ action = TCP_ACK + TCP_RST; ++ break; ++ case TCP_CLOSE_WAIT: ++ debug_cond(DEBUG_INT_STATE, "TCP_CLOSE_WAIT (%x)\n", tcp_flags); ++ action = TCP_DATA; /* Wait for app */ ++ break; ++ case TCP_FIN_WAIT_2: ++ debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_2 (%x)\n", tcp_flags); ++ if (tcp_fin) ++ action = TCP_DATA; ++ if (tcp_syn) ++ action = TCP_DATA; ++ if (tcp_ack) { ++ action = TCP_PUSH | TCP_ACK; ++ tcp_state = TCP_CLOSED; ++ puts("\n"); ++ } ++ break; ++ case TCP_FIN_WAIT_1: ++ debug_cond(DEBUG_INT_STATE, "TCP_FIN_WAIT_1 (%x)\n", tcp_flags); ++ if (tcp_fin) { ++ action = TCP_ACK | TCP_FIN; ++ tcp_state = TCP_FIN_WAIT_2; ++ } ++ if (tcp_syn) ++ action = TCP_RST; ++ if (tcp_ack) { ++ tcp_state = TCP_CLOSED; ++ tcp_seq_num = tcp_seq_num + 1; ++ } ++ break; ++ case TCP_CLOSING: ++ debug_cond(DEBUG_INT_STATE, "TCP_CLOSING (%x)\n", tcp_flags); ++ if (tcp_fin) ++ action = TCP_DATA; ++ if (tcp_syn) ++ action = TCP_RST; ++ if (tcp_ack) { ++ action = TCP_PUSH; ++ tcp_state = TCP_CLOSED; ++ puts("\n"); ++ } ++ break; ++ } ++ return action; ++} ++ ++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; ++ u32 tcp_ack_num; ++ struct in_addr action_and_state; ++ ++ int tcp_hdr_len; ++ int 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); ++ ++ debug_cond(DEBUG_DEV_PKT, ++ "In__________________________________________\n"); ++ ++ b->ip.hdr.ip_src = net_server_ip; ++ b->ip.hdr.ip_dst = net_ip; ++ b->ip.hdr.ip_sum = 0x0000; ++ 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 = 0x0000; ++ 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 = (b->ip.hdr.tcp_hlen >> 2); ++ 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); ++ ++ /* ++ * State-altering command to be sent. ++ * The packet sequence and ack numbers are in the tcp_seq_num ++ * and tcp_ack_num variables. The current packet, its position ++ * in the data stream, is the in the range of those variables. ++ * ++ * In the "application push" invocation, the TCP header with all ++ * its information is pointed to by the packet pointer. ++ * ++ * In the typedef ++ * void rxhand_tcp(uchar *pkt, unsigned int dport, ++ * struct in_addr sip, unsigned int sport, ++ * unsigned int len); ++ * *pkt is the pointer to the payload ++ * dport is used for tcp_seg_num ++ * action_and_state.s_addr is used for TCP state ++ * sport is used for tcp_ack_num (which is unused by the app) ++ * pkt_ length is the payload length. ++ * ++ * TCP_PUSH from the state machine with a payload length of 0 is a ++ * connect or disconnect event ++ */ ++ ++ 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=%d,Ack=%d,Pay%d)\n", ++ tcp_action, tcp_seq_num, tcp_ack_num, payload_len); ++ ++ action_and_state.s_addr = tcp_action; ++ (*tcp_packet_handler) ((uchar *)b + pkt_len - payload_len, ++ tcp_seq_num, action_and_state, ++ tcp_ack_num, payload_len); ++ ++ } else if (tcp_action != TCP_DATA) { ++ debug_cond(DEBUG_DEV_PKT, ++ "TCP Action (action=%x,Seq=%d,Ack=%d,Pay=%d)\n", ++ tcp_action, tcp_seq_num, tcp_ack_num, 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_seq_num, tcp_ack_num); ++ } ++} diff --git a/u-boot-patches/U-Boot-v12-3-3-Add-wget-application.diff b/u-boot-patches/U-Boot-v12-3-3-Add-wget-application.diff new file mode 100644 index 0000000..78f6d7b --- /dev/null +++ b/u-boot-patches/U-Boot-v12-3-3-Add-wget-application.diff @@ -0,0 +1,560 @@ +diff --git a/cmd/Kconfig b/cmd/Kconfig +index 45c83359ad..fb9c894363 100644 +--- a/cmd/Kconfig ++++ b/cmd/Kconfig +@@ -1192,6 +1192,13 @@ config CMD_NFS + help + Boot image via network using NFS protocol. + ++config CMD_WGET ++ bool "wget" ++ select TCP ++ help ++ Download kernel, or other files, from a web server over TCP. ++ Fast file transfer over networks with latenc ++ + config CMD_MII + bool "mii" + help +diff --git a/cmd/net.c b/cmd/net.c +index f83839c35e..f5fde849c4 100644 +--- a/cmd/net.c ++++ b/cmd/net.c +@@ -113,6 +113,19 @@ U_BOOT_CMD( + ); + #endif + ++#if defined(CONFIG_CMD_WGET) ++static int do_wget(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) ++{ ++ return netboot_common(WGET, cmdtp, argc, argv); ++} ++ ++U_BOOT_CMD( ++ wget, 3, 1, do_wget, ++ "boot image via network using HTTP protocol", ++ "[loadAddress] [[hostIPaddr:]path and image name]" ++); ++#endif ++ + static void netboot_update_env(void) + { + char tmp[22]; +diff --git a/include/net.h b/include/net.h +index ba96267eb8..14b013083d 100644 +--- a/include/net.h ++++ b/include/net.h +@@ -539,7 +539,7 @@ extern int net_restart_wrap; /* Tried all network devices */ + + enum proto_t { + BOOTP, RARP, ARP, TFTPGET, DHCP, PING, DNS, NFS, CDP, NETCONS, SNTP, +- TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT ++ TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WGET + }; + + extern char net_boot_file_name[1024];/* Boot File name */ +diff --git a/include/net/wget.h b/include/net/wget.h +new file mode 100644 +index 0000000000..61bdd851f9 +--- /dev/null ++++ b/include/net/wget.h +@@ -0,0 +1,19 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Duncan Hare Copyright 2017 ++ */ ++ ++void wget_start(void); /* Begin wget */ ++ ++enum WGET_STATE { ++ WGET_CLOSED, ++ WGET_CONNECTING, ++ WGET_CONNECTED, ++ WGET_TRANSFERRING, ++ WGET_TRANSFERRED ++}; ++ ++#define DEBUG_WGET 0 /* Set to 1 for debug messges */ ++#define SERVER_PORT 80 ++#define WGET_RETRY_COUNT 30 ++#define WGET_TIMEOUT 2000UL +diff --git a/net/Makefile b/net/Makefile +index 237023407f..882af145aa 100644 +--- a/net/Makefile ++++ b/net/Makefile +@@ -23,8 +23,8 @@ obj-$(CONFIG_CMD_PING) += ping.o + obj-$(CONFIG_CMD_RARP) += rarp.o + obj-$(CONFIG_CMD_SNTP) += sntp.o + obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o +-obj-$(CONFIG_UDP_FUNCTION_FASTBOOT) += fastboot.o + obj-$(CONFIG_TCP) += tcp.o ++obj-$(CONFIG_CMD_WGET) += wget.o + + # Disable this warning as it is triggered by: + # sprintf(buf, index ? "foo%d" : "foo", index) +diff --git a/net/net.c b/net/net.c +index 8ac1ff050f..39707d5b18 100644 +--- a/net/net.c ++++ b/net/net.c +@@ -109,6 +109,7 @@ + #include "sntp.h" + #endif + #include ++#include + + /** BOOTP EXTENTIONS **/ + +@@ -494,6 +495,11 @@ restart: + nfs_start(); + break; + #endif ++#if defined(CONFIG_CMD_WGET) ++ case WGET: ++ wget_start(); ++ break; ++#endif + #if defined(CONFIG_CMD_CDP) + case CDP: + cdp_start(); +@@ -1540,7 +1546,8 @@ void copy_filename(char *dst, const char *src, int size) + + #if defined(CONFIG_CMD_NFS) || \ + defined(CONFIG_CMD_SNTP) || \ +- defined(CONFIG_CMD_DNS) ++ defined(CONFIG_CMD_DNS) || \ ++ defined(CONFIG_CMD_WGET) + /* + * make port a little random (1024-17407) + * This keeps the math somewhat trivial to compute, and seems to work with +diff --git a/net/wget.c b/net/wget.c +new file mode 100644 +index 0000000000..8238ec52c4 +--- /dev/null ++++ b/net/wget.c +@@ -0,0 +1,426 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * WGET/HTTP support driver based on U-BOOT's nfs.c ++ * Copyright Duncan Hare 2017 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++const char bootfile1[] = "GET "; ++const char bootfile3[] = " HTTP/1.0\r\n\r\n"; ++const char http_eom[] = "\r\n\r\n"; ++const char http_ok[] = "200"; ++const char content_len[] = "Content-Length"; ++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. ++ */ ++ ++static struct pkt_qd pkt_q[PKTBUFSRX / 4]; ++static int pkt_q_idx; ++static unsigned long content_length; ++static unsigned int packets; ++ ++static unsigned int initial_data_seq_num; ++ ++static enum WGET_STATE wget_state; ++ ++static char *image_url; ++static unsigned int wget_timeout = WGET_TIMEOUT; ++ ++static void wget_timeout_handler(void); ++ ++static enum net_loop_state wget_loop_state; ++ ++/* Timeout retry parameters */ ++static u8 retry_action; ++static unsigned int retry_tcp_ack_num; ++static unsigned int retry_tcp_seq_num; ++static int retry_len; ++ ++static inline int store_block(uchar *src, unsigned int offset, unsigned int len) ++{ ++ ulong newsize = offset + len; ++ uchar *ptr; ++ ++#ifdef CONFIG_SYS_DIRECT_FLASH_WGET ++ int i, rc = 0; ++ ++ for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) { ++ /* start address in flash? */ ++ if (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)(load_addr + offset), len); ++ if (rc) { ++ flash_perror(rc); ++ return -1; ++ } ++ } else { ++#endif /* CONFIG_SYS_DIRECT_FLASH_WGET */ ++ ++ ptr = map_sysmem(load_addr + offset, len); ++ memcpy(ptr, src, len); ++ unmap_sysmem(ptr); ++ ++#ifdef CONFIG_SYS_DIRECT_FLASH_WGET ++ } ++#endif ++ if (net_boot_file_size < (offset + len)) ++ net_boot_file_size = newsize; ++ return 0; ++} ++ ++/* ++ * 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; ++ unsigned int tcp_ack_num = retry_tcp_ack_num; ++ unsigned int tcp_seq_num = retry_tcp_seq_num; ++ int len = retry_len; ++ uchar *ptr; ++ uchar *offset; ++ ++ tcp_ack_num = tcp_ack_num + len; ++ ++ switch (wget_state) { ++ case WGET_CLOSED: ++ debug_cond(DEBUG_WGET, "wget: send SYN\n"); ++ 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 = offset + strlen(bootfile1); ++ ++ memcpy(offset, image_url, strlen(image_url)); ++ offset = offset + strlen(image_url); ++ ++ memcpy(offset, &bootfile3, strlen(bootfile3)); ++ offset = offset + strlen(bootfile3); ++ net_send_tcp_packet((offset - ptr), SERVER_PORT, our_port, ++ TCP_PUSH, tcp_seq_num, tcp_ack_num); ++ 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_ack_num, ++ unsigned int tcp_seq_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("%s", error_message); ++ printf("%s", "wget: Transfer Fail\n"); ++ net_set_timeout_handler(0, NULL); ++ wget_send(action, tcp_seq_num, tcp_ack_num, 0); ++} ++ ++void wget_success(u8 action, unsigned int tcp_seq_num, ++ unsigned int tcp_ack_num, int len, int packets) ++{ ++ printf("Packets received %d, Transfer Successful\n", packets); ++ wget_send(action, tcp_seq_num, tcp_ack_num, len); ++} ++ ++/* ++ * 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(); ++ } ++} ++ ++static void wget_connected(uchar *pkt, unsigned int tcp_seq_num, ++ struct in_addr action_and_state, ++ unsigned int tcp_ack_num, unsigned int len) ++{ ++ u8 action = action_and_state.s_addr; ++ uchar *pkt_in_q; ++ char *pos; ++ int hlen; ++ int i; ++ ++ pkt[len] = '\0'; ++ pos = strstr((char *)pkt, http_eom); ++ ++ if (pos == 0) { ++ debug_cond(DEBUG_WGET, ++ "wget: Connected, data before Header %p\n", pkt); ++ pkt_in_q = (void *)load_addr + 0x20000 + (pkt_q_idx * 0x800); ++ memcpy(pkt_in_q, pkt, len); ++ 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++; ++ } else { ++ debug_cond(DEBUG_WGET, "wget: Connected HTTP Header %p\n", pkt); ++ 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); ++ ++ wget_state = WGET_TRANSFERRING; ++ ++ 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: Connctd pkt %p hlen %x\n", ++ pkt, hlen); ++ initial_data_seq_num = tcp_seq_num + hlen; ++ ++ pos = strstr((char *)pkt, content_len); ++ if (!pos) { ++ content_length = -1; ++ } else { ++ pos = 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) ++ store_block(pkt + hlen, 0, len - hlen); ++ debug_cond(DEBUG_WGET, ++ "wget: Connected Pkt %p hlen %x\n", ++ pkt, hlen); ++ ++ for (i = 0; i < pkt_q_idx; i++) { ++ store_block(pkt_q[i].pkt, ++ pkt_q[i].tcp_seq_num - ++ initial_data_seq_num, ++ pkt_q[i].len); ++ debug_cond(DEBUG_WGET, ++ "wget: Connctd pkt Q %p len %x\n", ++ pkt_q[i].pkt, pkt_q[i].len); ++ } ++ } ++ } ++ wget_send(action, tcp_seq_num, tcp_ack_num, len); ++} ++ ++ /* ++ * In the "application push" invocation, the TCP header with all ++ * its information is pointed to by the packet pointer. ++ * ++ * in the typedef ++ * void rxhand_tcp(uchar *pkt, unsigned int dport, ++ * struct in_addr sip, unsigned int sport, ++ * unsigned int len); ++ * *pkt is the pointer to the payload ++ * dport is used for tcp_seg_num ++ * action_and_state.s_addr is used for TCP state ++ * sport is used for tcp_ack_num (which is unused by the app) ++ * pkt_ length is the payload length. ++ */ ++static void wget_handler(uchar *pkt, unsigned int tcp_seq_num, ++ struct in_addr action_and_state, ++ unsigned int tcp_ack_num, unsigned int len) ++{ ++ enum TCP_STATE wget_tcp_state = tcp_get_tcp_state(); ++ u8 action = action_and_state.s_addr; ++ ++ net_set_timeout_handler(wget_timeout, wget_timeout_handler); ++ packets++; ++ ++ switch (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=%x, Ack=%x\n", ++ len, tcp_seq_num, tcp_ack_num); ++ if (len == 0) { ++ 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=%x, len=%x\n", ++ tcp_seq_num, len); ++ if (len == 0) { ++ wget_fail("Image not found, no data returned\n", ++ tcp_seq_num, tcp_ack_num, action); ++ } else { ++ wget_connected(pkt, tcp_seq_num, action_and_state, ++ 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 (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); ++ return; ++ } ++ ++ switch (wget_tcp_state) { ++ case TCP_FIN_WAIT_2: ++ wget_send(TCP_ACK, tcp_seq_num, tcp_ack_num, len); ++ case TCP_SYN_SENT: ++ 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 */ ++ 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); ++ break; ++ } ++} ++ ++void wget_start(void) ++{ ++ debug_cond(DEBUG_WGET, "%s\n", __func__); ++ ++ image_url = strchr(net_boot_file_name, ':'); ++ if (image_url > 0) { ++ web_server_ip = string_to_ip(net_boot_file_name); ++ ++image_url; ++ } 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 << 9); ++ print_size(net_boot_file_expected_size_in_blocks << 9, ""); ++ } ++ debug_cond(DEBUG_WGET, ++ "\nwget:Load address: 0x%lx\nLoading: *\b", load_addr); ++ ++ net_set_timeout_handler(wget_timeout, wget_timeout_handler); ++ tcp_set_tcp_handler(wget_handler); ++ ++ wget_timeout_count = 0; ++ wget_state = WGET_CLOSED; ++ ++ our_port = random_port(); ++ ++ /* ++ * Zero out server ether to forece apr resolution in case ++ * the server ip for the previous u-boot commsnd, for exanple dns ++ * is not the same as the web server ip. ++ */ ++ ++ memset(net_server_ethaddr, 0, 6); ++ ++ wget_send(TCP_SYN, 0, 0, 0); ++} diff --git a/u-boot/bl31.bin b/u-boot/bl31.bin new file mode 100755 index 0000000000000000000000000000000000000000..d8f6e34331bb111a4e87736cde11b453bd18f9c2 GIT binary patch literal 32776 zcmeHw3wTuJo%jEoGYPo?lM4_-BxeFx7`Y_j79zr#Ng|*KaT1kUw=(qlNXLn2(%TBz-%%vHL+a=y z>+%SZ)J4{5kylqJ5myH7cigL3r5@vbxJ-#S(w9eU?UZ3_PsKGU!;wBMqRO=1fvYi# z_G`F%aovSyZzkEjV5$HC6A(3c4&- z+HX6jq+d{|OC_zF)>NFzOQOxm2~@?fXofbVN_2D-jMvBO9Z5l#fU`YC|L@`9DF=an z063%1(PNmCl%;jc6{OqRr)5Z4)ZGbM5z2y!b%_M}0GA>HU6_WU7neu!rJxIa?V%T^ zMMA$^7IEVF&@Xc%eR;uNx*=P%vu)S*NzpzYr`A1?7j#`n)rKT1rT2kH`9$5e>w_){ z_lw(3Jl>C_faC(0{|r;~@Qi!W|F9-G65Vh&G-nYR}z zrEn#9^UfFKu;&J?`@N~eJQDNqZJ(g$PsNx~ehVJ`I#vG~<|9$iB~vitrzH4vSX!@j zdnHI!qu#fBg8m-*cM@&(WNYuEu8MJUa$WWW{T%Ab z@y;|2Y1FOZSt>K`@(dc{cV(7wx2&gbmYs{+ej1~973!JZ@A;JQP`xEQb9nf{AZQ=M z6LjyGHU`gSY5E-C+0SwW8GR7+oQ%0IfcyluOv_jRS(=3BqZyQO6!Y%`t&ZO{wUT-C zGTv_iU5?3<>sJjA3tf!zYtg+JJ0MdLk1zP)D+Nwv;Y`3|9$nn_bHN+tD~~rA zWS8}W%lniZdtJ0Gzhm6}jQ^iNbLK&ZYRE@7-kHvm=9PaH+VM?`)qst~GTg zP5=D_+D{%zXPwJ?d*bFW_?q|O#LazEI&&zA(wR>L{EWMtZrh$vDJ4m%Y|F-$v3)Sg zEU|WG*#PW2lPq>QN~8P_J*TGMNg351*{)c$p&Goq$x;uT zswk-GzTwZoJ{=1)->5x9=ehiBJa|RHUUNLA+iu{-?Rb9N2DVS=rSHrn{X|-vzO0(8 zQ%k1q!JI---5o%^@7I%sUTZu~P|{7Dn2E8W07yK0PJc%?!@TouJ=M3uhB*MqQN1{Bc z(}q7|oC5D=>aT*X;(2nG{(JLzahCqS@yvMG-ptYm1nqwomIye}ZkGi-SHK_WbFZ{C z6)`*dQp=5!f+f4(b%$3EXAWF%6Evj(EYd zKFo10=F^Wm;u!mfh+p2x)P~q5@!T(Nd-idv$krE(rz}B>nkjk#;6UGmj4&?Gf@jQQ z$l=~vqH|6n{V`lML?=s8wj0krTn9;hp-+*!ZdMSpS(5w?mP^DH676`xj1f5I*nxPC z;|$YAsIaY|I)A53^G+dIFt zv)2K618f)10dwnCEr=6P&T`oQ4;>;VVO+$#Jy+}J(`bKNI?okxNutpYwr#)UxvGfY zPp~}&%_x+s{|RlmZynl8tmDg)Dw|}=V*EK)GkF-*t&_gzC+QvI;&khWSLsxBmHt)G zdIj__1YT*DU`G6W-iB?Oj2goBD7XuoEEn@%AL% zj=Tn&Gyq*-d0-hgWt!#rB{A1$!u^T`b?^slhY)vP!1F`VdGuf&*8-;<&=UuqUje*3 zqx0eT)r?h{4nlW>Q-wX1h=x3ndxE@gu^2KKPcxO}W8pN>_KT1aY3F^t><3sDWyI5{ z?^e@rW#gKGs{q$B>Xz=iPsnRN>g6v6dz~z!;0JK)mj9)rS6&`E&(D%&cdsPmdc=oF zyYB0iWyIKMLy$#=QJo%i@=Wdia1O{}Lf>YhzT+!`IvhI-iC?ox~=V>~X`@NUB3u|Db8edB_681gq_ z2cQRrjeCBYp3F4;GUyTfvX~c-r2=n)UFJDzcvgVVju&ZjKkN_64pUj4{y{3%aZ)fN z53#5v`<9ZP#Aw}aReu|Gko{4*x8r_;4x;}ndHPvyBL_1$W~Do`GfFK%*l)$IVa$$> zFZY&$x5m7IOG9pkS@$S-s3pafC4m;-!kQI$%fQvyo{Ja+bmM2bx`>|-O=mgbIbAL0 z#I%FYatYp{ekb0Ur_5)AhvSy8?*0i!bn7;iUZrUx=(1Fe}}wplivw%hdYGR+{@fE~l#13ass zgB*XW(%@o2-{)=m186Ir=SdVR8xB3p1jIkh>cXtAAC7XuTRyz zDbMCO*JSF`UsR^YPXzuPtMHoW zvDRsoHv-r1Q(`KQ{kmeeCxg~!G3K*?|0aAn`%gtuQl16gr6*Z?-^4rSr9+ZGkgK}Q&B%V!H}N-^bu=w=yhh-cM&)8-SqLVA!bWNU#10Ua()KW z95mp0?*>g?Q)Y;{KT5{j6NL;W#^x`Rly36F2|AXNlDd)m(l|Tf<8B4>iplG_PSz~9~MMgvUKJ!Klz>4-u!Ix*t|;# z#@smm$d`9TV_~hk18XwoDK!v$qFf(j+lu-i9riQrGE;)R9xdpq6tMSHyC%_j@gna^Up>J%%nD#-ymkRE4aV#$S zsv(zz_}qqhN$ewWABn-933Fb<273wI_A-BrcrLNaHe;4fPtDSm6!3M2_5{WmisvuW zjy+jxSk}gPwL|G*%%{PJM7P+?CVFR%9&!oHr6vtB^#%WJwWwmI`rc1X@d zS!P!$-kAn=lnH!7vvoh7ci`gn1=E&uR2(Zmk%~OF7VZ_Y({o>M&)m>?e$EThW*f&( zQgOtK_IYVpeht2dtUGh065QcKMLUu7;PJOTM&AIwOhFGf!d9$r4)G2hWJ`FOK34Ayng+0mGAy29g zVXW!kxtxi0*PMC!1&o6=#b%~E%PXCntJ{v?Le7E9C~LmH9&5Ue3=MOqy*m@NAv!Z( zuS6N=@R+Af@VDR})=$Pdo%!@5?1Qk4VwkpBDPn!WI zy%P0hSpT2K{0fZq>t5P4M_+=vU*cT@Z?DGM$MZ#PsE6o?z;VW0eLm{BjA+HnrmZ}T zx$*j-4!mGL#q;K|xDV6R*RVi$VqIYCTcFe4LcJF-94E1_u--Gse3hJ#CFHR$+t;&D zuO5%SPcPKz>qYvr=zGjT5!g1J_hPs|UrYn$v1tRM>+2XEzMcXD2Zr-vq5fmQxd8vw z3)!djfkAUFCM=F!1uV$HL_nTY)Q52%1z#ao-3sk=v79AXcDj`8@B0&Q*>aOA z%hC|%(zuMyG}=5JF^P>5aP2y9unTKjne4KxE^(wePpv>6!@;^l*+roa8XCYl9%Bvd ztmeF)BYa3sXWi+NlY+gbJ@F+|RwrUzwrk8QleV18*!(f`KZH0=%s+R5UM^@%U7d-B z{Xzb3C?si%JzdySO5Eu>h&fPuB7BO3{C#f3(_ztPgBQZ)i#+>@$2VYX3HE43TKjq8 zxaaZuQ$*fOkuF;WFOkQZ)RhgM zX7U`XIUkX2-yvt6eHJukdYbzN<~~kewKf!ZW7{c-GXGZ)`Okcdwv;vnirHVUf)2Vr zL~M<_nxrjdy+r)P&qMG>N7XcKs1JAa84^03wn#5QzT;B?nd$O^ zuSKZO9!KfF#k^0W&VxGG+@rH2`o4Y1TrhW(+KA2=4KM8rj~S)7M-V(mhG zQzK@q9g|zwS2gMrV)~l2R3G4d))`B&#$BR+8#r^0OU9V2uaDpz<77aOu-6-(M~dnK zU~+qwu~)&11*XqoxV$e3oS9z&e$NtpC*b8lPuZtUN4c+}SZAN=MQpVKdG3SJawlwv z@2O(3r^agq8*r(Sl|8-`WzSA4Ev-($8f${qy#e;1b~5)(thcH6<+v%K-l={$hyAHd z?Z^TD(!GekI?;!7wVlXuN~(lC2*dzrLx_2J@1z#6zh~8!cIJ|w;~*ZV2L9OsxhhvJ z_G6HVL%_|A{zbkPa;ZY%axke3`wBW}z4vGMdbX|+E}D+T`B2U^Kf#G3FN z;a$**iuJ{Dv=Z&c&?*FcxI7c#wr#>21k9Oz`p zWn)H9>b~Ak`l0jhuuer|Mq%sXWJ~lvw^VnIGh$#P*EGj92S*`ZK_*?kZJiI=H7R4coFuw#kr1)+eUo1sRK-tDTt@Ct;G=)`e5?c1{w%m z0Do)5xc{4BH+jNuw!zRXRoZ#Lzvq;u%XO*v=Ka503KWb{_jC9 zCWFRnVm!zK@1}Ge&o9B6Y*N>2u#J2+$D}pKc1(Z9Z)`d1e!R?%!V~c@%NFZX3{SR? zymnxDeQ1Tg4WN&4ekaTRJFMF~%a$RSG!pD zmjegbemm1j!MZRA`6pxT2A`1GwHWnm+fChIzu=4N#!R%6*uEoAWYbD;4(HjJZHdnv z8FU-LmB(d#MLhU1dL!%~<6EWZo+$ko=ieg69*g7nzY>ljSA>0W4LPHoye9XMt|Ffh zZxb))$9W#{eq7L;#~k=90H3GCc?8g2v7h051n3^~#fWpg!WU=SRpE<@ zBV+yXEa({PMGyS(gP?`z&oMH1!9JN|w+F!=_UHeKwxj)WCj5aBCkwu@Uk2ae{W9yX z=_|}~<{#69vc>*<;sPK5#<}lfi4tP}EN! zLVMpM38Vcq^z%x7`Vj29>8D#jkLLG{wKR0_oQfEy2YOGSYY_MU0KLt)kYl9kCFq-$ zsQ(6fZbMr_E`j|5(;j-Cv0d;d=xRdlQiJXLZ@?G!Aa3n*aodO;W}X_cuSLYZY=hb6 z#@S%Ih=*aPIUeR%m~F8c4;ORnGL9C-$Dj8gm*x|8cO(YB9<+;ZQ-yb)7h0={}|Ijl*700=XcJJP%ic|AdhCd znj!EMzW*b+)3g*_o`ku-nF3lT>FY4}`230+a%biSaXu{;GYh)ipQaCR{Kb7=Nzyg+ zU69LX-#7-7?QOt^&zU>bo}2+C-|orp$#FtkeM0 zn|WX(|BLk)Z0&+LSU>s;uzCzwFBz~5e$=y{;IU?54Nuy6F$d}t=V2Kae!nQj{z)t@ z;dKk^1J*4AZdr@-rh@KMcHgB7S1pAulNQn8>Pa#oj_}-_(?6whwF@c+R{=X5R+i za+JsLZBNzn5pSDg3HSv``a^*KJJ>3=r<6pu@H!BiY{C}E*hlj1O%*ov@3DqqJK;qg z>%x4&uaWb1jJph9=8o!<7!z~pm_KGrPqMxZW7;s@A(zIz*Vdx0wiT)Kdf{vue%k?+dCtxkjGGyX*xv}_L zA6|pwn+UP*hP|sQj+@4g*Mspw(397r{9s<}&v+n*$2ImOJ|)|ofQ z8+(a}?JMyfz}^n?HVe;_aBay~u-7qBKUzIOM-q6y$1Y;6#T=VX6ExQLar|B_YeT#4 z@eJ~sS4F$PA<}oqlh5wNzJQ6JDHHMh<^3_Xj~tUQek@OX-r0k5_8W2M^Zij;!1j-v z^JKa(yzhR9bqepD#M$N70P}d~G32YIyKKnIShiWr|@YL2lqH3t>-2!(5i3pWxho6y|QWIpDRa5B$tF-n3CR(1ml)kO!9p zUvZ@|&m!(cjQ7zo_gnP=_Rl<~$Et6{nC5u#>pk8(eH*;7U`+FzG2_EDH~lNePaN-_ zK&*mu6h|c3kN!*J7JmzU%ovlbojHabiQflF?AjPTmjQXpVqSBO#o&+9WwD%FQoUW< zrv(4WwF_rv67YNtt{pRI^D6KXU*4VDeR86{Yn87UXKk#z?w`Jga|IgK4xC%R8Ze(^ z8HMlVJ*j@==C|VAeC|^cy8aIDgQ(-@6L@CZ^f~DHGF(dVsSF->M>cJK9z09Hc#n}) ze-z`@V!TJAK_6Q^W;77<^tj&J`=(=m}8@%cZVND%!@de774qK zILT+7B)$iPKGxryLRIaFbju{j4(AyLqP%+;vdiCuFmvC z>+;xKEaw(J2$aWivJ9^ueOVsv15Abon_xeB?@u45?NcBAy-a(4eU~~IDkA-X`Lq=4 zs68_fLmFpUGVT8k{DarhQp6XFf<%j97jYhP(w<_-62r`Z4j~t3hfIr@ljmsC$5%W_ zKZNfD1)VA~`3&8ZS72jvA+NbuS27;I=6A%sk`rf<0GnYRyzAh@7vCFxkoP_(DN|DO zl&k8G4-HotdTh}%UtF-~J@iE$@D@)Zbsq!V+c0hk^qlu|Uxv+`igQ+XJZlkqN-_Nr zdwr7qS7Pl+cJIAkdLSQtoSH>1C23FDCht=Yz_DSSTZ{7o3SjU)*&g_SLu9$B!_tv@ z17!CX+m(l}*|cFWj}p&T!|#<_=oa1stiy8|{5tEEwtf1;Zp1Udc@Kp;55&tLrhS3IJ3Dx==smpiS(pDD<8$NJr-lv4?!q}kKI0&4 zz(MHtZ{ROqy=TE7$Hx3kNe`YmPDQ*aV!3!HoY`Q~FU znfD@PtJJ*{^UB9}u>78nJ;0;i)%HyWUyuI5vrp_Lqt7L*L0-i=;75QFluPvYFF;O_ ztLejc42)Yf=c^Y^7T@}Ce8lT4;Na@RT#Y@lNnQ5iJNzv0WuKY|z2Wwp;|TKl z0qs$Lgg7SPToc~;nPryS#o(%A;BK0tV?z6X6oq>Zc#eXL_M_n9-GKWvWK6QdM~eM+ zUK1U8lypnd@dE+SI~bH6ma(?Wn?-Mx-zlX#dnnym@a3G2yQUZ8yFETig8>^EywOt(ep=Q0)jb4%bnqsvJA^%eYQ*YZX>f_OjC9+% zFYP;sm~#?oL$@k}KM7$^@gIRdY}5;U#(Ob*SH4Q=hkzUFbpzmr@<7ub+aA{U<5CyyIH^D`=nvN-RxudxJg~S2WR5rxC|~lXAfc+*4KC7%h=Y%!~A+29(;}wK0*%y zZ;q`^xH9G`je$*=r-XB%oYP?(;$glR2b1mE*twx^_giE7jGdb~c0bc4j=nr5kInh) z_%Yv%8{V^|N3fi)JF%i+K0FZ6-@Z-&fNg~0ncpeOTz z^{o^AjF^?Wi1qmCY!R=o1Re37#d?KU;;aLGIYyK=O)qAhG1hgX>t<~76Wx?UyuY$^IPKNXVq%)}*G=q0wHIJ5o&;Td_8vy30>B#(1wlc%_PlUh6^@QaV> zVE3TUUxiKP^|j|v=N<9Bi1C$y2je2XT?I@HzK&rs-uXD6&vwu7SN{^;fwLlPlQ=ed zA8|Xrp+f8_;j9$S$6_s2y+Kams7$DQ<5Z3f^%Khq3)5MU@yl~`(bOAj_W+|o^w90we{SjzVpicTDTgt zag%iRarglWV!e*sCVcXd6_oXc1bs8@HT%Y~vC*QQ;ypWP(phH`RqX(b*Wu^gcz+ak z8M$5LSGpbp%Yu@V-14yT9@%{%_#n5U8R=*4(GU5pl=9s=eGH1=j*vz?}w^Mq)JbDVpmUzZK>*j$(SYfEg;I6lrA9a~aYb&59B0pB05W4;`m z^}<{{iO#`rl-6uRrb9pSF&^R!;ftMgjn3m!&o%l?v=h(3GjCbNd6V`g&3$)H#cz~A zZ$ek;)mT&fj@N@}Rp%whQ=a9v=zYC$PrEaE_nhav4%0$?GeOLi=g8;q*`CaX+%x@R zW1O3+=i}QqF%EJi{96Sf;L6|J1#xG(dT{6bwT8QK&S`Z1%z-n;Ha@$WMsJyO@a@mh z%`pbV?-*bn>vQxj%t5m9UK3=RA`KWdn_?+po@$B>v7rvfJ<*6M4wj^0z-Kb>x`8QDd&P zdodUJ3&CEChPVf3fp%X)9du~D6a<}ypE7 z-<}eF*PR+3+?W=Azj=K4!@DPhGaSW{^E+thH6^d>kB}W}>-tj@x4TbSb0&7(lqy;O zt5dfAM_o(ti@UX<|B#E99K-hjZ`4h#Y+ilWpjBHQDa4h5GhHXk(!;$$8d@o9Pj*VA zPsDy~pS0rTw*ZsBC3x#AG<1HKHgs;~E#ZvxX%VY74SlCYu;-Qqo@Ma;(l%bV1T*@S z&X>85hPn5Rqwv5)DK?MG&G9eIjp@@jP6HeV;5b2#((vn_D+8^{L8}`;tCgV7YS8CK z(B~%5XAS6cGw8E6d}-ynaJnNqa&G0SFyn>2lHR)@$FE{uD^D(ul;e7gBz>3?&IaK# zpCfH4zWG>q2Kg@D=lvsMw&(sxEpzaEX16w2fIEI4>Oqd#F98PQ#`}9oGbo%gM+&Dc zl*1F2DPiQ$r?A|My=TNFECbMA_;##4iH3UKr2?*>47y#ZUFcnE;rcYRW13#6t>*$+ zxt?W<$CcBhuq8_lC$=l$xxl$EjgBB@)xjjOCuza@^+P-(j(L#VkISW@@4reF-FRmk z#AVhp3TLd5!dX>vc=DY}_=#wFb`6Dd8l-S;s~r9j$~N>mk1(wJ2B;!QD~`;@WiMS8xd~Squ0~uFaL#Z( zuE)`C74FGY5=n6^gDw|G($r$;adBkAp)wI-aforsPEpH2v_C`E7dk0L_|Zr4zWdiy z@GzG{hC2C3GpoNM~!Wx{)t)~ zX~AB6_mm}}FSoe?eCxrphWLx^P&&pjVTN9#7TZL`plIKdCJkOvQpB3%9NLXTyCC8+ z8Rs#axNEfHtO;ky%Jx|XEtN~1y-MG)^Y|V!r4zc-1AKQ{oP+SADSRH@ zitq?cGfZr@8*8ROl40Q+i}`OUebh$T)jeN!YW^^E-P9NQ|kp-mzW13!=ZYlFOB zGzEEXUeg78zkvMbZ!Y2YI^ffG;OzM@=*hnELM1M=nTq!C$3JFz_WXe=`o{%(rvn${ zXHU1B zcfdZeUAh4_EMZ`GZ!qcS2P}IN9;P1B7oq=SNxwKyJ3M@pW$85d8O+j(F}EZ0P{(;g z5Ap9lP3ua)9`$2ZdMP;L=Lc|JY9Hs1NlfNR7glr^2Mi}>*SdmZ2teoyKEemh_f)>SVN zP5+vDZz|{^@4-IP*+(HOW=wL$K3kG5+Xvrl9C-u&?^g<0e>I8r@Hfx15IZ;%Zrmfy zIB~$1r0v5whPTdwekq6vINp?KX(S)zPAlm%3QA9X+1hi*SMeKDR@={WR!hnDYZ}H^ zrrS>B2)v}cm+>8|zJpTEHeh|22N+z=a9FJrPo82s%A^pvp3?- zej-+X??>z3h_AorqxGD7nGIa0|J|*pe1khq@g9hHuUjwPku#pce4(u10WauG>hS}N z6UXqjtepoC%l780Cl28EzYY-YT$h)ooezTFq35Y!C+v$GFz_w*9>~B8J2ZN$9(8%! zcRtQ}1bnx07BW4=)%HW6R``IIos+QTGbM@U~$R9;*x?TbJVh0 zpITIa{|bv~OX0kNSv9rQZ5wBo_?B*1w8&RbL>udTTNb!|Ep2rh+=1HGww6&PHML$} zYuy%aYb|;Gjczm^)pT^(Wg52tu)C%%&>DkocDZa}T~l3aY@m&`t?riErkc8@jqbXp z)>^;6t)4N5Ktp|ZU6FMx<|@ev{carc%Y(5T$yTIVWJRYq^i0(GuhR#&4oHMgo8>Y8d) zSJ~>yDs@A%UoEY;RlU2eb(4B?#p*JbTF^eTp*@AF8x|D0TN=Erf(T8u?Vv=XcVn%) zwgoKP&`gys$IYsvD0(k6?xob$RNvHmcaz#!+t}>is`_g;)-^X#85+l`Dw|8mv3TM9 zRXEtQY7wnnSGBsd9EdvCESOtZqPp6enjjczbCWtVpw61vK3lCb9yNj6>=a@S2U;Pz z?z(_`i?^Y!W)=(2Y+{7x2ilqz71Z5b*itxO%mVN2b%q9jEZmS;xOUxIHwbyFt9%55 zvZ@k{nKDwawplH!Qk%SuwIEenQ_UPTP+!;5!st~ud;K+PprzJVx1r9b3I&@N*R`^= zVs(jH)7IFyRSmSZRjZr4O*IX*e#lT`OG9mAZ4)K{+Nce+TWTAi*y0a!6WG=?8^mWF zZEgyDWMYCdEsdB@qqp7NvT19;-MnFg+q+>y!1f|-FfVBJ&`Z9Ho>i|}Qj5IwcpSZBT&y|1~+crRXK+}oq?KaAeLccW3>8oe)! z-eZ!;NU4FXf!5kaHL$6zwFZ(>0_xN?G^pU>mRfb*JkT)KzLII7RvH~#wV-i{+T2p> zHweeFY!J03p75p3O&jVqw)vS!l@+Bb-dp|64Iq6AtzLV>I#vlTj5TtsuTU#)UR@f; zJa2=))?2ex^+lW2)(BOpx_xzABkofIyf53-Rq2eINNIChgQ0wE2US!u=qOr19y3$@ zZB27q>mX^~ni_v?ATUCPnE{AL`%GwMj55a9Fq}}}nKe)|FB?-a4V5MQBTY@cx>mLhv1XCc`RO@S1TbosrLX4?UuDRaD3+F-&YWddC78ubO1F;Qt?Tikqy}G5j zp%sQh-QaI-R14=7Exe6E7S3C+__jEP82#cqKo#5O%?!+JL$fiXRyQ}-kMJ@|izaV% z1Nc+{thio<8-kW>9n~4GrZzCQ?&JDamAlF-)zxNKwWay)QBq@!Bvf4uwE2AC(uTGM zF#;@%(8)kui#w|6QB~%Wz?L|B%g z@{Pw7F2nc4#w~U?it){!`8T$Kct^Z#{%O$%^B&}PtVcf4pWzGqwJlzMtvlWoK!Uf9 zus&+P*?`9MELyf2QeOuRzlWs)f-dAdQ0w1P2O9o`<_w|;G_Liv`D>}E+281Gpg1gq zH!^&aY1>zpR~fb)Uf5k(<)|v3HKxPd<(uYtTU-6JKZ0t7m?z`O&ar&W0=2dsDhrW? zH@dso-%#^WeAmJ~!mP8DN0nD7Y(x7TSfENcVMBW_@C5O5goY_@Q_hSzHJ)-}Pr z`w;RV3Sn!3LB_ngtGBuvTk2~3Tn@1`n|-8Q%svhVTOqSC_ic$Xx3*z{o2P3~JBF8v z5GKxF);0MW+G=VAp7Wzn-HjU?Tjy;eUu!!BTHKAzHNwl@ysoU=U0LBSUsGVrZB+9( zrC8l0{P~L_83HqQvZAxv+pdosSbQ;n10cfaB)*u^n9n2L|8z4f*Jt?q_qUp+z|5GB3<5J$cHYa41gW`+G;raB!rmAgLgUbC+B zCil&b^=^b%mC@F7m&515?7~Gi)<(;yp{{y94-=Sg3=zGFp&1X>aL z!a9lQzz81-IFyJlC>&c*G`2vz^MarWe4o2qgftvviICFmxZ#F1w?P0PHE{j@D>W)4 zBRsfLn<7Z3VL9eUv7gV_i+`d)4dVmCMgKNc8*#}=Z!~_1-WlQMCL@64U{ZvX@e>Cn zn&RID&|??!lC7uYnm+hvNmw^{rNX2jM&a7m!a)DUX9KpLqw&FL2$$m18wLU zn;S#lc=>5oBf=l8idP_tueq_YnT3sYnXW*5UT za*TMq9f@@wncIlh7E21p1|~=AR`q6YOG%u2R#nuj28B!1HN46~L^($gf(@eZ&b){e z9)kIN?oorUdqT#2O7wl(LF0YhS~N;jlE>%LG%J#bXUF~bc^W4Rxn^53N->0SfgSbx zVyas1E-PQVy1Z<3nYl=-a3ET)WTKAhQh94>X?bPkm26%#R2bE5G}MoC7lL}DT!fem z6O+XiV%s7tu_2-H#Zglv(n<)-qQcEf$<~cA$-jb>i69z6#-sSKv~GnHY^wG8-AGa_ zQ!Caus%}{4y4hX2&Q*?oZe3d?7P@HtG16|}6I~X&n_J?RPZdZCAeL*yVle>0YDGZT z((G@AxAY1VYA!I>ZP=i0!pb3j1^P+*ip8nfo6g}6|J333`E%!HoNG9DuH;;uqq4Hx zB{B@^vY9o@k<8;zxshp%sL>rwl?WoY17)oHVMws1A)mJa87-{w>+r;F{q8{BJ+2+8d1nR4W!j?#9M84tHpECDH(hzh*6%O|y8Nh(M^dL4-nB`d_0~mR7BCxo>tm zS67MDRsqct#tNgL)=^f5N=M~Q?uEtfs&($g3!;s<%@!|MC0bNgI7$)ct-hn23`-21 zT_D05uh;L}gk%rTD2gGQ7S1PekuY2^S?j@Yca_7rro56du5hd@cO!MNa&38;$h^5* zya?4VSFzSp1=PNf+80s#VrpMP?Zwo-6jfY}`a(1)M2kW+DMXt>G%7?ZWO&;P(XI&XiWm;s z6`@@b+7+Q)5!w}@T@l(9VK*%r>f#>dG$!6ESWc}*6ihYVx(!gNI-@ku5-=WW4auO^ z)@px&ke}LU%?9Wu%M+q|QBp`oTSRPJ;H}1@fgg}1Gais(H6Aw9;UPwKWVE96_qBKh zGKJ&|1V(^i6u^#-Vd7CJFe(&vQLGEe8+$Tlg+{y>5{=BK0;9~|jM4(k+UH+TgvTPP z+Ym4xv92`$$9j8F$itG} zar9mly>s1Xf1gp{GYWi0fxiX?`tUti9?nF3TGzjQ%Hwx^%Hsz=&cIi+ zA)N0QzenxH8O47*gRyZ&C4@6MUwi{+zwq7TJ2(r)=i3#W6`X-N5QT%@- z%Kz_Ns#^ay#?Kso`0bDKe_kB@|M2Jk8UKIxHvV66p8$7r{8ukC{)D*kpZOn-|5J>g s`3c6SPtfLX$^XAb4DlI^&nWO21wNy|XB7C10-sUf|1Sz~Hj=OZ3nLHF3;+NC literal 0 HcmV?d00001