Merge tag 'sound-2023-01-rc4' of https://source.denx.de/u-boot/custodians/u-boot-efi

Pull request for sound-2023-01-rc4

* Avoid endless loop and amend unit test
* Add man-page for the sound command
* Fix sandbox sound driver
diff --git a/Makefile b/Makefile
index 2d24ac3..de57463 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@
 VERSION = 2023
 PATCHLEVEL = 01
 SUBLEVEL =
-EXTRAVERSION = -rc2
+EXTRAVERSION = -rc3
 NAME =
 
 # *DOCUMENTATION*
diff --git a/cmd/Kconfig b/cmd/Kconfig
index a4b6fc6..d93731f 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1847,6 +1847,13 @@
 	help
 	  Send ICMP ECHO_REQUEST to network host
 
+config CMD_PING6
+	bool "ping6"
+	depends on IPV6
+	default y if (CMD_PING && IPV6)
+	help
+	  Send ICMPv6 ECHO_REQUEST to network host
+
 config CMD_CDP
 	bool "cdp"
 	help
diff --git a/cmd/net.c b/cmd/net.c
index f6d9f5e..0e9f200 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -14,6 +14,7 @@
 #include <env.h>
 #include <image.h>
 #include <net.h>
+#include <net6.h>
 #include <net/udp.h>
 #include <net/sntp.h>
 #include <net/ncsi.h>
@@ -45,12 +46,22 @@
 	return ret;
 }
 
+#if IS_ENABLED(CONFIG_IPV6)
+U_BOOT_CMD(
+	tftpboot,	4,	1,	do_tftpb,
+	"boot image via network using TFTP protocol\n"
+	"To use IPv6 add -ipv6 parameter or use IPv6 hostIPaddr framed "
+	"with [] brackets",
+	"[loadAddress] [[hostIPaddr:]bootfilename] [" USE_IP6_CMD_PARAM "]"
+);
+#else
 U_BOOT_CMD(
 	tftpboot,	3,	1,	do_tftpb,
 	"load file via network using TFTP protocol",
 	"[loadAddress] [[hostIPaddr:]bootfilename]"
 );
 #endif
+#endif
 
 #ifdef CONFIG_CMD_TFTPPUT
 static int do_tftpput(struct cmd_tbl *cmdtp, int flag, int argc,
@@ -306,6 +317,17 @@
 	if (s != NULL)
 		image_load_addr = hextoul(s, NULL);
 
+	if (IS_ENABLED(CONFIG_IPV6)) {
+		use_ip6 = false;
+
+		/* IPv6 parameter has to be always *last* */
+		if (!strcmp(argv[argc - 1], USE_IP6_CMD_PARAM)) {
+			use_ip6 = true;
+			/* It is a hack not to break switch/case code */
+			--argc;
+		}
+	}
+
 	if (parse_args(proto, argc, argv)) {
 		bootstage_error(BOOTSTAGE_ID_NET_START);
 		return CMD_RET_USAGE;
@@ -313,6 +335,19 @@
 
 	bootstage_mark(BOOTSTAGE_ID_NET_START);
 
+	if (IS_ENABLED(CONFIG_IPV6) && !use_ip6) {
+		char *s, *e;
+		size_t len;
+
+		s = strchr(net_boot_file_name, '[');
+		e = strchr(net_boot_file_name, ']');
+		if (s && e) {
+			len = e - s;
+			if (!string_to_ip6(s + 1, len - 1, &net_server_ip6))
+				use_ip6 = true;
+		}
+	}
+
 	size = net_loop(proto);
 	if (size < 0) {
 		bootstage_error(BOOTSTAGE_ID_NET_NETLOOP_OK);
@@ -368,6 +403,32 @@
 );
 #endif
 
+#if IS_ENABLED(CONFIG_CMD_PING6)
+int do_ping6(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
+{
+	if (string_to_ip6(argv[1], strlen(argv[1]), &net_ping_ip6))
+		return CMD_RET_USAGE;
+
+	use_ip6 = true;
+	if (net_loop(PING6) < 0) {
+		use_ip6 = false;
+		printf("ping6 failed; host %pI6c is not alive\n",
+		       &net_ping_ip6);
+		return 1;
+	}
+
+	use_ip6 = false;
+	printf("host %pI6c is alive\n", &net_ping_ip6);
+	return 0;
+}
+
+U_BOOT_CMD(
+	ping6,  2,      1,      do_ping6,
+	"send ICMPv6 ECHO_REQUEST to network host",
+	"pingAddress"
+);
+#endif /* CONFIG_CMD_PING6 */
+
 #if defined(CONFIG_CMD_CDP)
 
 static void cdp_update_env(void)
diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig
index cff166b..b69e053 100644
--- a/configs/sandbox64_defconfig
+++ b/configs/sandbox64_defconfig
@@ -258,3 +258,4 @@
 CONFIG_UNIT_TEST=y
 CONFIG_UT_TIME=y
 CONFIG_UT_DM=y
+CONFIG_IPV6=y
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index 07fe0c3..404e4a3 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -333,3 +333,4 @@
 CONFIG_UNIT_TEST=y
 CONFIG_UT_TIME=y
 CONFIG_UT_DM=y
+CONFIG_IPV6=y
diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig
index 01234dc..7237efe 100644
--- a/configs/sandbox_flattree_defconfig
+++ b/configs/sandbox_flattree_defconfig
@@ -216,3 +216,4 @@
 CONFIG_UNIT_TEST=y
 CONFIG_UT_TIME=y
 CONFIG_UT_DM=y
+CONFIG_IPV6=y
diff --git a/doc/develop/release_cycle.rst b/doc/develop/release_cycle.rst
index 2bd037f..6ee67ef 100644
--- a/doc/develop/release_cycle.rst
+++ b/doc/develop/release_cycle.rst
@@ -67,7 +67,7 @@
 
 * U-Boot v2023.01-rc2 was released on Mon 21 November 2022.
 
-.. * U-Boot v2023.01-rc3 was released on Mon 21 November 2022.
+* U-Boot v2023.01-rc3 was released on Mon 05 December 2022.
 
 .. * U-Boot v2023.01-rc4 was released on Mon 05 December 2022.
 
diff --git a/include/env_callback.h b/include/env_callback.h
index 1eae0ef..85e7fe2 100644
--- a/include/env_callback.h
+++ b/include/env_callback.h
@@ -51,6 +51,15 @@
 #define NET_CALLBACKS
 #endif
 
+#ifdef CONFIG_IPV6
+#define NET6_CALLBACKS \
+	"ip6addr:ip6addr," \
+	"serverip6:serverip6," \
+	"gatewayip6:gatewayip6,"
+#else
+#define NET6_CALLBACKS
+#endif
+
 #ifdef CONFIG_BOOTSTD
 #define BOOTSTD_CALLBACK	"bootmeths:bootmeths,"
 #else
@@ -65,6 +74,7 @@
 	ENV_DOT_ESCAPE ENV_FLAGS_VAR ":flags," \
 	"baudrate:baudrate," \
 	NET_CALLBACKS \
+	NET6_CALLBACKS \
 	BOOTSTD_CALLBACK \
 	"loadaddr:loadaddr," \
 	SILENT_CALLBACK \
diff --git a/include/env_flags.h b/include/env_flags.h
index 313cb8c..718d727 100644
--- a/include/env_flags.h
+++ b/include/env_flags.h
@@ -67,6 +67,15 @@
 #define NET_FLAGS
 #endif
 
+#ifdef CONFIG_IPV6
+#define NET6_FLAGS \
+	"ip6addr:s," \
+	"serverip6:s," \
+	"gatewayip6:s"
+#else
+#define NET6_FLAGS
+#endif
+
 #ifndef CONFIG_ENV_OVERWRITE
 #define SERIAL_FLAGS "serial#:so,"
 #else
@@ -76,6 +85,7 @@
 #define ENV_FLAGS_LIST_STATIC \
 	ETHADDR_FLAGS \
 	NET_FLAGS \
+	NET6_FLAGS \
 	SERIAL_FLAGS \
 	CONFIG_ENV_FLAGS_LIST_STATIC
 
diff --git a/include/ndisc.h b/include/ndisc.h
new file mode 100644
index 0000000..f6f8eb6
--- /dev/null
+++ b/include/ndisc.h
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2013 Allied Telesis Labs NZ
+ * Chris Packham, <judge.packham@gmail.com>
+ *
+ * Copyright (C) 2022 YADRO
+ * Viacheslav Mitrofanov <v.v.mitrofanov@yadro.com>
+ */
+
+#ifndef __NDISC_H__
+#define __NDISC_H__
+
+#include <ndisc.h>
+
+/* struct nd_msg - ICMPv6 Neighbour Discovery message format */
+struct nd_msg {
+	struct icmp6hdr	icmph;
+	struct in6_addr	target;
+	__u8		opt[0];
+};
+
+/* struct echo_msg - ICMPv6 echo request/reply message format */
+struct echo_msg {
+	struct icmp6hdr	icmph;
+	__u16		id;
+	__u16		sequence;
+};
+
+/* Neigbour Discovery option types */
+enum {
+	__ND_OPT_PREFIX_INFO_END	= 0,
+	ND_OPT_SOURCE_LL_ADDR		= 1,
+	ND_OPT_TARGET_LL_ADDR		= 2,
+	ND_OPT_PREFIX_INFO		= 3,
+	ND_OPT_REDIRECT_HDR		= 4,
+	ND_OPT_MTU			= 5,
+	__ND_OPT_MAX
+};
+
+/* IPv6 destination address of packet waiting for ND */
+extern struct in6_addr net_nd_sol_packet_ip6;
+/* MAC destination address of packet waiting for ND */
+extern uchar *net_nd_packet_mac;
+/* pointer to packet waiting to be transmitted after ND is resolved */
+extern uchar *net_nd_tx_packet;
+/* size of packet waiting to be transmitted */
+extern int net_nd_tx_packet_size;
+/* the timer for ND resolution */
+extern ulong net_nd_timer_start;
+/* the number of requests we have sent so far */
+extern int net_nd_try;
+
+#ifdef CONFIG_IPV6
+/**
+ * ndisc_init() - Make initial steps for ND state machine.
+ * Usually move variables into initial state.
+ */
+void ndisc_init(void);
+
+/**
+ * ndisc_receive() - Handle ND packet
+ *
+ * @et:		pointer to incoming packet
+ * @ip6:	pointer to IPv6 header
+ * @len:	incoming packet length
+ * Return: 0 if handle successfully, -1 if unsupported/unknown ND packet type
+ */
+int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len);
+
+/**
+ * ndisc_request() - Send ND request
+ */
+void ndisc_request(void);
+
+/**
+ * ndisc_init() - Check ND response timeout
+ *
+ * Return: 0 if no timeout, -1 otherwise
+ */
+int ndisc_timeout_check(void);
+#else
+static inline void ndisc_init(void)
+{
+}
+
+static inline int
+ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len)
+{
+	return -1;
+}
+
+static inline void ndisc_request(void)
+{
+}
+
+static inline int ndisc_timeout_check(void)
+{
+	return 0;
+}
+#endif
+
+#endif /* __NDISC_H__ */
diff --git a/include/net.h b/include/net.h
index e0c7804..1a99009 100644
--- a/include/net.h
+++ b/include/net.h
@@ -560,8 +560,8 @@
 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, WOL, UDP, NCSI, WGET
+	BOOTP, RARP, ARP, TFTPGET, DHCP, PING, PING6, DNS, NFS, CDP, NETCONS,
+	SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT, WOL, UDP, NCSI, WGET
 };
 
 extern char	net_boot_file_name[1024];/* Boot File name */
diff --git a/include/net6.h b/include/net6.h
new file mode 100644
index 0000000..9b3de02
--- /dev/null
+++ b/include/net6.h
@@ -0,0 +1,432 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2013 Allied Telesis Labs NZ
+ * Chris Packham, <judge.packham@gmail.com>
+ *
+ * Copyright (C) 2022 YADRO
+ * Viacheslav Mitrofanov <v.v.mitrofanov@yadro.com>
+ */
+
+#ifndef __NET6_H__
+#define __NET6_H__
+
+#include <net.h>
+#include <linux/ctype.h>
+
+/* struct in6_addr - 128 bits long IPv6 address */
+struct in6_addr {
+	union {
+		u8	u6_addr8[16];
+		__be16	u6_addr16[8];
+		__be32	u6_addr32[4];
+	} in6_u;
+
+#define s6_addr		in6_u.u6_addr8
+#define s6_addr16	in6_u.u6_addr16
+#define s6_addr32	in6_u.u6_addr32
+};
+
+#define IN6ADDRSZ	sizeof(struct in6_addr)
+#define INETHADDRSZ	sizeof(net_ethaddr)
+
+#define PROT_IP6	0x86DD	/* IPv6 protocol */
+#define PROT_ICMPV6	58	/* ICMPv6 protocol*/
+
+#define IPV6_ADDRSCOPE_INTF	0x01
+#define IPV6_ADDRSCOPE_LINK	0x02
+#define IPV6_ADDRSCOPE_AMDIN	0x04
+#define IPV6_ADDRSCOPE_SITE	0x05
+#define IPV6_ADDRSCOPE_ORG	0x08
+#define IPV6_ADDRSCOPE_GLOBAL	0x0E
+
+#define USE_IP6_CMD_PARAM	"-ipv6"
+
+/**
+ * struct ipv6hdr - Internet Protocol V6 (IPv6) header.
+ *
+ * IPv6 packet header as defined in RFC 2460.
+ */
+struct ip6_hdr {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+	u8	priority:4,
+		version:4;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+	u8	version:4,
+		priority:4;
+#else
+#error  "Please fix <asm/byteorder.h>"
+#endif
+	u8		flow_lbl[3];
+	__be16		payload_len;
+	u8		nexthdr;
+	u8		hop_limit;
+	struct in6_addr	saddr;
+	struct in6_addr	daddr;
+};
+#define IP6_HDR_SIZE (sizeof(struct ip6_hdr))
+
+/* struct udp_hdr - User Datagram Protocol header */
+struct udp_hdr {
+	u16		udp_src;	/* UDP source port		*/
+	u16		udp_dst;	/* UDP destination port		*/
+	u16		udp_len;	/* Length of UDP packet		*/
+	u16		udp_xsum;	/* Checksum			*/
+} __packed;
+
+/*
+ * Handy for static initialisations of struct in6_addr, atlhough the
+ * c99 '= { 0 }' idiom might work depending on you compiler.
+ */
+#define ZERO_IPV6_ADDR { { { 0x00, 0x00, 0x00, 0x00, \
+			  0x00, 0x00, 0x00, 0x00, \
+			  0x00, 0x00, 0x00, 0x00, \
+			  0x00, 0x00, 0x00, 0x00 } } }
+
+#define IPV6_LINK_LOCAL_PREFIX	0xfe80
+
+/* hop limit for neighbour discovery packets */
+#define IPV6_NDISC_HOPLIMIT             255
+#define NDISC_TIMEOUT			5000UL
+#define NDISC_TIMEOUT_COUNT             3
+
+/* struct icmp6hdr - Internet Control Message Protocol header for IPV6 */
+struct icmp6hdr {
+	u8	icmp6_type;
+#define IPV6_ICMP_ECHO_REQUEST			128
+#define IPV6_ICMP_ECHO_REPLY			129
+#define IPV6_NDISC_ROUTER_SOLICITATION		133
+#define IPV6_NDISC_ROUTER_ADVERTISEMENT		134
+#define IPV6_NDISC_NEIGHBOUR_SOLICITATION	135
+#define IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT	136
+#define IPV6_NDISC_REDIRECT			137
+	u8	icmp6_code;
+	__be16	icmp6_cksum;
+
+	/* ICMPv6 data */
+	union {
+		__be32	un_data32[1];
+		__be16	un_data16[2];
+		u8	un_data8[4];
+
+		/* struct icmpv6_echo - echo request/reply message format */
+		struct icmpv6_echo {
+			__be16		identifier;
+			__be16		sequence;
+		} u_echo;
+
+		/* struct icmpv6_nd_advt - Neighbor Advertisement format */
+		struct icmpv6_nd_advt {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+			__be32		reserved:5,
+					override:1,
+					solicited:1,
+					router:1,
+					reserved2:24;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+			__be32		router:1,
+					solicited:1,
+					override:1,
+					reserved:29;
+#else
+#error	"Please fix <asm/byteorder.h>"
+#endif
+		} u_nd_advt;
+
+		/* struct icmpv6_nd_ra - Router Advertisement format */
+		struct icmpv6_nd_ra {
+			u8		hop_limit;
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+			u8		reserved:6,
+					other:1,
+					managed:1;
+
+#elif defined(__BIG_ENDIAN_BITFIELD)
+			u8		managed:1,
+					other:1,
+					reserved:6;
+#else
+#error	"Please fix <asm/byteorder.h>"
+#endif
+			__be16		rt_lifetime;
+		} u_nd_ra;
+	} icmp6_dataun;
+#define icmp6_identifier	icmp6_dataun.u_echo.identifier
+#define icmp6_sequence		icmp6_dataun.u_echo.sequence
+#define icmp6_pointer		icmp6_dataun.un_data32[0]
+#define icmp6_mtu		icmp6_dataun.un_data32[0]
+#define icmp6_unused		icmp6_dataun.un_data32[0]
+#define icmp6_maxdelay		icmp6_dataun.un_data16[0]
+#define icmp6_router		icmp6_dataun.u_nd_advt.router
+#define icmp6_solicited		icmp6_dataun.u_nd_advt.solicited
+#define icmp6_override		icmp6_dataun.u_nd_advt.override
+#define icmp6_ndiscreserved	icmp6_dataun.u_nd_advt.reserved
+#define icmp6_hop_limit		icmp6_dataun.u_nd_ra.hop_limit
+#define icmp6_addrconf_managed	icmp6_dataun.u_nd_ra.managed
+#define icmp6_addrconf_other	icmp6_dataun.u_nd_ra.other
+#define icmp6_rt_lifetime	icmp6_dataun.u_nd_ra.rt_lifetime
+};
+
+extern struct in6_addr const net_null_addr_ip6;	/* NULL IPv6 address */
+extern struct in6_addr net_gateway6;	/* Our gateways IPv6 address */
+extern struct in6_addr net_ip6;	/* Our IPv6 addr (0 = unknown) */
+extern struct in6_addr net_link_local_ip6;	/* Our link local IPv6 addr */
+extern u32 net_prefix_length;	/* Our prefixlength (0 = unknown) */
+extern struct in6_addr net_server_ip6;	/* Server IPv6 addr (0 = unknown) */
+extern struct in6_addr net_ping_ip6; /* the ipv6 address to ping */
+extern bool use_ip6;
+
+#if IS_ENABLED(CONFIG_IPV6)
+/**
+ * string_to_ip6() - Convert IPv6 string addr to inner IPV6 addr format
+ *
+ * Examples of valid strings:
+ *	2001:db8::0:1234:1
+ *	2001:0db8:0000:0000:0000:0000:1234:0001
+ *	::1
+ *	::ffff:192.168.1.1
+ *
+ * Examples of invalid strings
+ *	2001:db8::0::0          (:: can only appear once)
+ *	2001:db8:192.168.1.1::1 (v4 part can only appear at the end)
+ *	192.168.1.1             (we don't implicity map v4)
+ *
+ * @s:		IPv6 string addr format
+ * @len:	IPv6 string addr length
+ * @addr:	converted IPv6 addr
+ * Return: 0 if conversion successful, -EINVAL if fail
+ */
+int string_to_ip6(const char *s, size_t len, struct in6_addr *addr);
+
+/**
+ * ip6_is_unspecified_addr() - Check if IPv6 addr is not set i.e. is zero
+ *
+ * @addr:	IPv6 addr
+ * Return:  0 if addr is not set, -1 if is set
+ */
+int ip6_is_unspecified_addr(struct in6_addr *addr);
+
+/**
+ * ip6_is_our_addr() - Check if IPv6 addr belongs to our host addr
+ *
+ * We have 2 addresses that we should respond to. A link local address and a
+ * global address. This returns true if the specified address matches either
+ * of these.
+ *
+ * @addr:	addr to check
+ * Return: 0 if addr is our, -1 otherwise
+ */
+int ip6_is_our_addr(struct in6_addr *addr);
+
+/**
+ * ip6_addr_in_subnet() - Check if two IPv6 addresses are in the same subnet
+ *
+ * @our_addr:		first IPv6 addr
+ * @neigh_addr:		second IPv6 addr
+ * @prefix_length:	network mask length
+ * Return: 0 if two addresses in the same subnet, -1 otherwise
+ */
+int ip6_addr_in_subnet(struct in6_addr *our_addr, struct in6_addr *neigh_addr,
+		       u32 prefix_length);
+
+/**
+ * ip6_make_lladd() - rMake up IPv6 Link Local address
+ *
+ * @lladdr:	formed IPv6 Link Local address
+ * @enetaddr:	MAC addr of a device
+ */
+void ip6_make_lladdr(struct in6_addr *lladr, unsigned char const enetaddr[6]);
+
+/**
+ * ip6_make_snma() - aMake up Solicited Node Multicast Address from IPv6 addr
+ *
+ * @mcast_addr:	formed SNMA addr
+ * @ip6_addr:	base IPv6 addr
+ */
+void ip6_make_snma(struct in6_addr *mcast_addr, struct in6_addr *ip6_addr);
+
+/**
+ * ip6_make_mult_ethdstaddr() - Make up IPv6 multicast addr
+ *
+ * @enetaddr:	MAC addr of a device
+ * @mcast_addr:	formed IPv6 multicast addr
+ */
+void ip6_make_mult_ethdstaddr(unsigned char enetaddr[6],
+			      struct in6_addr *mcast_addr);
+
+/**
+ * csum_partial() - Compute an internet checksum
+ *
+ * @buff:	buffer to be checksummed
+ * @len:	length of buffer
+ * @sum:	initial sum to be added in
+ * Return: internet checksum of the buffer
+ */
+unsigned int csum_partial(const unsigned char *buff, int len, unsigned int sum);
+
+/**
+ * csum_ipv6_magic() - Compute checksum of IPv6 "psuedo-header" per RFC2460 section 8.1
+ *
+ * @saddr:	source IPv6 addr
+ * @daddr:	destination IPv6 add
+ * @len:	data length to be checksummed
+ * @proto:	IPv6 above protocol code
+ * @csum:	upper layer checksum
+ * Return: computed checksum
+ */
+unsigned short int csum_ipv6_magic(struct in6_addr *saddr,
+				   struct in6_addr *daddr, u16 len,
+				   unsigned short proto, unsigned int csum);
+
+/**
+ * ip6_add_hdr() - Make up IPv6 header
+ *
+ * @xip:	pointer to IPv6 header to be formed
+ * @src:	source IPv6 addr
+ * @dest:	destination IPv6 addr
+ * @nextheader:	next header type
+ * @hoplimit:	hop limit
+ * @payload_len: payload length
+ * Return: IPv6 header length
+ */
+int ip6_add_hdr(uchar *xip, struct in6_addr *src, struct in6_addr *dest,
+		int nextheader, int hoplimit, int payload_len);
+
+/**
+ * net_send_udp_packet6() - Make up UDP packet and send it
+ *
+ * @ether:	destination MAC addr
+ * @dest:	destination IPv6 addr
+ * @dport:	destination port
+ * @sport:	source port
+ * @len:	UDP packet length
+ * Return: 0 if send successfully, -1 otherwise
+ */
+int net_send_udp_packet6(uchar *ether, struct in6_addr *dest, int dport,
+			 int sport, int len);
+
+/**
+ * net_ip6_handler() - Handle IPv6 packet
+ *
+ * @et:		pointer to the beginning of the packet
+ * @ip6:	pointer to the beginning of IPv6 protocol
+ * @len:	incoming packet len
+ * Return: 0 if handle packet successfully, -EINVAL in case of invalid protocol
+ */
+int net_ip6_handler(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len);
+
+/**
+ * net_copy_ip6() - Copy IPv6 addr
+ *
+ * @to:		destination IPv6 addr
+ * @from:	source IPv6 addr
+ */
+static inline void net_copy_ip6(void *to, const void *from)
+{
+	memcpy((void *)to, from, sizeof(struct in6_addr));
+}
+#else
+static inline int
+string_to_ip6(const char *s, size_t len, struct in6_addr *addr)
+{
+	return -EINVAL;
+}
+
+static inline int ip6_is_unspecified_addr(struct in6_addr *addr)
+{
+	return -1;
+}
+
+static inline int ip6_is_our_addr(struct in6_addr *addr)
+{
+	return -1;
+}
+
+static inline int
+ip6_addr_in_subnet(struct in6_addr *our_addr, struct in6_addr *neigh_addr,
+		   u32 prefix_length)
+{
+	return -1;
+}
+
+static inline void
+ip6_make_lladdr(struct in6_addr *lladdr, unsigned char const enetaddr[6])
+{
+}
+
+static inline void
+ip6_make_snma(struct in6_addr *mcast_addr, struct in6_addr *ip6_addr)
+{
+}
+
+static inline void
+ip6_make_mult_ethdstaddr(unsigned char enetaddr[6],
+			 struct in6_addr *mcast_addr)
+{
+}
+
+static inline unsigned int
+csum_partial(const unsigned char *buff, int len, unsigned int sum)
+{
+	return 0;
+}
+
+static inline unsigned short
+csum_ipv6_magic(struct in6_addr *saddr,
+		struct in6_addr *daddr, u16 len,
+		unsigned short proto, unsigned int csum)
+{
+	return 0;
+}
+
+static inline unsigned int
+ip6_add_hdr(uchar *xip, struct in6_addr *src, struct in6_addr *dest,
+	    int nextheader, int hoplimit, int payload_len)
+{
+	return 0;
+}
+
+static inline int
+net_send_udp_packet6(uchar *ether, struct in6_addr *dest,
+		     int dport, int sport, int len)
+{
+	return -1;
+}
+
+static inline int
+net_ip6_handler(struct ethernet_hdr *et, struct ip6_hdr *ip6,
+		int len)
+{
+	return -EINVAL;
+}
+
+static inline void net_copy_ip6(void *to, const void *from)
+{
+}
+#endif
+
+#if IS_ENABLED(CONFIG_CMD_PING6)
+/* Send ping requset */
+void ping6_start(void);
+
+/**
+ * ping6_receive() - Handle reception of ICMPv6 echo request/reply
+ *
+ * @et:		pointer to incoming patcket
+ * @ip6:	pointer to IPv6 protocol
+ * @len:	packet length
+ * Return: 0 if success, -EINVAL in case of failure during reception
+ */
+int ping6_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len);
+#else
+static inline void ping6_start(void)
+{
+}
+
+static inline
+int ping6_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len)
+{
+	return -EINVAL;
+}
+#endif /* CONFIG_CMD_PING6 */
+
+#endif /* __NET6_H__ */
diff --git a/lib/net_utils.c b/lib/net_utils.c
index 72a3b09..4283c13 100644
--- a/lib/net_utils.c
+++ b/lib/net_utils.c
@@ -11,6 +11,7 @@
 
 #include <common.h>
 #include <net.h>
+#include <net6.h>
 
 struct in_addr string_to_ip(const char *s)
 {
@@ -43,6 +44,114 @@
 	return addr;
 }
 
+#if IS_ENABLED(CONFIG_IPV6)
+int string_to_ip6(const char *str, size_t len, struct in6_addr *addr)
+{
+	int colon_count = 0;
+	int found_double_colon = 0;
+	int xstart = 0;		/* first zero (double colon) */
+	int section_num = 7;	/* num words the double colon represents */
+	int i;
+	const char *s = str;
+	const char *const e = s + len;
+	struct in_addr zero_ip = {.s_addr = 0};
+
+	if (!str)
+		return -1;
+
+	/* First pass, verify the syntax and locate the double colon */
+	while (s < e) {
+		while (s < e && isxdigit((int)*s))
+			s++;
+		if (*s == '\0')
+			break;
+		if (*s != ':') {
+			if (*s == '.' && section_num >= 2) {
+				struct in_addr v4;
+
+				while (s != str && *(s - 1) != ':')
+					--s;
+				v4 = string_to_ip(s);
+				if (memcmp(&zero_ip, &v4,
+					   sizeof(struct in_addr)) != 0) {
+					section_num -= 2;
+					break;
+				}
+			}
+			/* This could be a valid address */
+			break;
+		}
+		if (s == str) {
+			/* The address begins with a colon */
+			if (*++s != ':')
+				/* Must start with a double colon or a number */
+				goto out_err;
+		} else {
+			s++;
+			if (found_double_colon)
+				section_num--;
+			else
+				xstart++;
+		}
+
+		if (*s == ':') {
+			if (found_double_colon)
+				/* Two double colons are not allowed */
+				goto out_err;
+			found_double_colon = 1;
+			section_num -= xstart;
+			s++;
+		}
+
+		if (++colon_count == 7)
+			/* Found all colons */
+			break;
+		++s;
+	}
+
+	if (colon_count == 0)
+		goto out_err;
+	if (*--s == ':')
+		section_num++;
+
+	/* Second pass, read the address */
+	s = str;
+	for (i = 0; i < 8; i++) {
+		int val = 0;
+		char *end;
+
+		if (found_double_colon &&
+		    i >= xstart && i < xstart + section_num) {
+			addr->s6_addr16[i] = 0;
+			continue;
+		}
+		while (*s == ':')
+			s++;
+
+		if (i == 6 && isdigit((int)*s)) {
+			struct in_addr v4 = string_to_ip(s);
+
+			if (memcmp(&zero_ip, &v4,
+				   sizeof(struct in_addr)) != 0) {
+				/* Ending with :IPv4-address */
+				addr->s6_addr32[3] = v4.s_addr;
+				break;
+			}
+		}
+
+		val = simple_strtoul(s, &end, 16);
+		if (end != e && *end != '\0' && *end != ':')
+			goto out_err;
+		addr->s6_addr16[i] = htons(val);
+		s = end;
+	}
+	return 0;
+
+out_err:
+	return -1;
+}
+#endif
+
 void string_to_enetaddr(const char *addr, uint8_t *enetaddr)
 {
 	char *end;
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index fe06aa2..530d808 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -450,10 +450,6 @@
  *       decimal for v4 and colon separated network-order 16 bit hex for v6)
  * - 'i' [46] for 'raw' IPv4/IPv6 addresses, IPv6 omits the colons, IPv4 is
  *       currently the same
- *
- * Note: IPv6 support is currently if(0)'ed out. If you ever need
- * %pI6, please add an IPV6 Kconfig knob, make your code select or
- * depend on that, and change the 0 below to CONFIG_IS_ENABLED(IPV6).
  */
 static char *pointer(const char *fmt, char *buf, char *end, void *ptr,
 		int field_width, int precision, int flags)
@@ -498,8 +494,7 @@
 		flags |= SPECIAL;
 		/* Fallthrough */
 	case 'I':
-		/* %pI6 currently unused */
-		if (0 && fmt[1] == '6')
+		if (IS_ENABLED(CONFIG_IPV6) && fmt[1] == '6')
 			return ip6_addr_string(buf, end, ptr, field_width,
 					       precision, flags);
 		if (fmt[1] == '4')
diff --git a/net/Kconfig b/net/Kconfig
index cb600fe..a1ec3f8 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -190,6 +190,16 @@
 	  This option should be turn on if you want to achieve the fastest
 	  file transfer possible.
 
+config IPV6
+	bool "IPv6 support"
+	help
+	  Enable IPv6 support. It includes Neighbour Discovery protocol, ICMPv6
+	  and auxiliary stuff to make it work. Since it is enabled u-boot
+	  network subsystem can get and handle incoming packets and send packets
+	  through IPv6 network. It allows to use environment variables such as
+	  ip6addr, serverip6. If a u-boot command is capable to parse an IPv6
+	  address and find it, it will force using IPv6 in the network stack.
+
 endif   # if NET
 
 config SYS_RX_ETH_BUFFER
diff --git a/net/Makefile b/net/Makefile
index 4f757a2..13eef04 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -20,9 +20,12 @@
 obj-$(CONFIG_DM_MDIO_MUX) += mdio-mux-uclass.o
 obj-$(CONFIG_NET)      += eth_common.o
 obj-$(CONFIG_CMD_LINK_LOCAL) += link_local.o
+obj-$(CONFIG_IPV6)     += ndisc.o
 obj-$(CONFIG_NET)      += net.o
+obj-$(CONFIG_IPV6)     += net6.o
 obj-$(CONFIG_CMD_NFS)  += nfs.o
 obj-$(CONFIG_CMD_PING) += ping.o
+obj-$(CONFIG_CMD_PING6) += ping6.o
 obj-$(CONFIG_CMD_PCAP) += pcap.o
 obj-$(CONFIG_CMD_RARP) += rarp.o
 obj-$(CONFIG_CMD_SNTP) += sntp.o
diff --git a/net/ndisc.c b/net/ndisc.c
new file mode 100644
index 0000000..3c0eeea
--- /dev/null
+++ b/net/ndisc.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Allied Telesis Labs NZ
+ * Chris Packham, <judge.packham@gmail.com>
+ *
+ * Copyright (C) 2022 YADRO
+ * Viacheslav Mitrofanov <v.v.mitrofanov@yadro.com>
+ */
+
+/* Neighbour Discovery for IPv6 */
+
+#include <common.h>
+#include <net.h>
+#include <net6.h>
+#include <ndisc.h>
+
+/* IPv6 destination address of packet waiting for ND */
+struct in6_addr net_nd_sol_packet_ip6 = ZERO_IPV6_ADDR;
+/* IPv6 address we are expecting ND advert from */
+static struct in6_addr net_nd_rep_packet_ip6 = ZERO_IPV6_ADDR;
+/* MAC destination address of packet waiting for ND */
+uchar *net_nd_packet_mac;
+/* pointer to packet waiting to be transmitted after ND is resolved */
+uchar *net_nd_tx_packet;
+static uchar net_nd_packet_buf[PKTSIZE_ALIGN + PKTALIGN];
+/* size of packet waiting to be transmitted */
+int net_nd_tx_packet_size;
+/* the timer for ND resolution */
+ulong net_nd_timer_start;
+/* the number of requests we have sent so far */
+int net_nd_try;
+
+#define IP6_NDISC_OPT_SPACE(len) (((len) + 2 + 7) & ~7)
+
+/**
+ * ndisc_insert_option() - Insert an option into a neighbor discovery packet
+ *
+ * @ndisc:	pointer to ND packet
+ * @type:	option type to insert
+ * @data:	option data to insert
+ * @len:	data length
+ * Return: the number of bytes inserted (which may be >= len)
+ */
+static int
+ndisc_insert_option(struct nd_msg *ndisc, int type, u8 *data, int len)
+{
+	int space = IP6_NDISC_OPT_SPACE(len);
+
+	ndisc->opt[0] = type;
+	ndisc->opt[1] = space >> 3;
+	memcpy(&ndisc->opt[2], data, len);
+	len += 2;
+
+	/* fill the remainder with 0 */
+	if (space - len > 0)
+		memset(&ndisc->opt[len], '\0', space - len);
+
+	return space;
+}
+
+/**
+ * ndisc_extract_enetaddr() - Extract the Ethernet address from a ND packet
+ *
+ * Note that the link layer address could be anything but the only networking
+ * media that u-boot supports is Ethernet so we assume we're extracting a 6
+ * byte Ethernet MAC address.
+ *
+ * @ndisc:	pointer to ND packet
+ * @enetaddr:	extracted MAC addr
+ */
+static void ndisc_extract_enetaddr(struct nd_msg *ndisc, uchar enetaddr[6])
+{
+	memcpy(enetaddr, &ndisc->opt[2], 6);
+}
+
+/**
+ * ndisc_has_option() - Check if the ND packet has the specified option set
+ *
+ * @ip6:	pointer to IPv6 header
+ * @type:	option type to check
+ * Return: 1 if ND has that option, 0 therwise
+ */
+static int ndisc_has_option(struct ip6_hdr *ip6, __u8 type)
+{
+	struct nd_msg *ndisc = (struct nd_msg *)(((uchar *)ip6) + IP6_HDR_SIZE);
+
+	if (ip6->payload_len <= sizeof(struct icmp6hdr))
+		return 0;
+
+	return ndisc->opt[0] == type;
+}
+
+static void ip6_send_ns(struct in6_addr *neigh_addr)
+{
+	struct in6_addr dst_adr;
+	unsigned char enetaddr[6];
+	struct nd_msg *msg;
+	__u16 len;
+	uchar *pkt;
+	unsigned short csum;
+	unsigned int pcsum;
+
+	debug("sending neighbor solicitation for %pI6c our address %pI6c\n",
+	      neigh_addr, &net_link_local_ip6);
+
+	/* calculate src, dest IPv6 addr and dest Eth addr */
+	ip6_make_snma(&dst_adr, neigh_addr);
+	ip6_make_mult_ethdstaddr(enetaddr, &dst_adr);
+	len = sizeof(struct icmp6hdr) + IN6ADDRSZ +
+	    IP6_NDISC_OPT_SPACE(INETHADDRSZ);
+
+	pkt = (uchar *)net_tx_packet;
+	pkt += net_set_ether(pkt, enetaddr, PROT_IP6);
+	pkt += ip6_add_hdr(pkt, &net_link_local_ip6, &dst_adr, PROT_ICMPV6,
+			   IPV6_NDISC_HOPLIMIT, len);
+
+	/* ICMPv6 - NS */
+	msg = (struct nd_msg *)pkt;
+	msg->icmph.icmp6_type = IPV6_NDISC_NEIGHBOUR_SOLICITATION;
+	msg->icmph.icmp6_code = 0;
+	memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16));
+	memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32));
+
+	/* Set the target address and llsaddr option */
+	net_copy_ip6(&msg->target, neigh_addr);
+	ndisc_insert_option(msg, ND_OPT_SOURCE_LL_ADDR, net_ethaddr,
+			    INETHADDRSZ);
+
+	/* checksum */
+	pcsum = csum_partial((__u8 *)msg, len, 0);
+	csum = csum_ipv6_magic(&net_link_local_ip6, &dst_adr,
+			       len, PROT_ICMPV6, pcsum);
+	msg->icmph.icmp6_cksum = csum;
+	pkt += len;
+
+	/* send it! */
+	net_send_packet(net_tx_packet, (pkt - net_tx_packet));
+}
+
+static void
+ip6_send_na(uchar *eth_dst_addr, struct in6_addr *neigh_addr,
+	    struct in6_addr *target)
+{
+	struct nd_msg *msg;
+	__u16 len;
+	uchar *pkt;
+	unsigned short csum;
+
+	debug("sending neighbor advertisement for %pI6c to %pI6c (%pM)\n",
+	      target, neigh_addr, eth_dst_addr);
+
+	len = sizeof(struct icmp6hdr) + IN6ADDRSZ +
+	    IP6_NDISC_OPT_SPACE(INETHADDRSZ);
+
+	pkt = (uchar *)net_tx_packet;
+	pkt += net_set_ether(pkt, eth_dst_addr, PROT_IP6);
+	pkt += ip6_add_hdr(pkt, &net_link_local_ip6, neigh_addr,
+			   PROT_ICMPV6, IPV6_NDISC_HOPLIMIT, len);
+
+	/* ICMPv6 - NA */
+	msg = (struct nd_msg *)pkt;
+	msg->icmph.icmp6_type = IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT;
+	msg->icmph.icmp6_code = 0;
+	memset(&msg->icmph.icmp6_cksum, 0, sizeof(__be16));
+	memset(&msg->icmph.icmp6_unused, 0, sizeof(__be32));
+	msg->icmph.icmp6_dataun.u_nd_advt.solicited = 1;
+	msg->icmph.icmp6_dataun.u_nd_advt.override = 1;
+	/* Set the target address and lltargetaddr option */
+	net_copy_ip6(&msg->target, target);
+	ndisc_insert_option(msg, ND_OPT_TARGET_LL_ADDR, net_ethaddr,
+			    INETHADDRSZ);
+
+	/* checksum */
+	csum = csum_ipv6_magic(&net_link_local_ip6,
+			       neigh_addr, len, PROT_ICMPV6,
+			       csum_partial((__u8 *)msg, len, 0));
+	msg->icmph.icmp6_cksum = csum;
+	pkt += len;
+
+	/* send it! */
+	net_send_packet(net_tx_packet, (pkt - net_tx_packet));
+}
+
+void ndisc_request(void)
+{
+	if (!ip6_addr_in_subnet(&net_ip6, &net_nd_sol_packet_ip6,
+				net_prefix_length)) {
+		if (ip6_is_unspecified_addr(&net_gateway6)) {
+			puts("## Warning: gatewayip6 is needed but not set\n");
+			net_nd_rep_packet_ip6 = net_nd_sol_packet_ip6;
+		} else {
+			net_nd_rep_packet_ip6 = net_gateway6;
+		}
+	} else {
+		net_nd_rep_packet_ip6 = net_nd_sol_packet_ip6;
+	}
+
+	ip6_send_ns(&net_nd_rep_packet_ip6);
+}
+
+int ndisc_timeout_check(void)
+{
+	ulong t;
+
+	if (ip6_is_unspecified_addr(&net_nd_sol_packet_ip6))
+		return 0;
+
+	t = get_timer(0);
+
+	/* check for NDISC timeout */
+	if ((t - net_nd_timer_start) > NDISC_TIMEOUT) {
+		net_nd_try++;
+		if (net_nd_try >= NDISC_TIMEOUT_COUNT) {
+			puts("\nNeighbour discovery retry count exceeded; "
+			     "starting again\n");
+			net_nd_try = 0;
+			net_set_state(NETLOOP_FAIL);
+		} else {
+			net_nd_timer_start = t;
+			ndisc_request();
+		}
+	}
+	return 1;
+}
+
+void ndisc_init(void)
+{
+	net_nd_packet_mac = NULL;
+	net_nd_tx_packet = NULL;
+	net_nd_sol_packet_ip6 = net_null_addr_ip6;
+	net_nd_rep_packet_ip6 = net_null_addr_ip6;
+	net_nd_tx_packet_size = 0;
+	net_nd_tx_packet = &net_nd_packet_buf[0] + (PKTALIGN - 1);
+	net_nd_tx_packet -= (ulong)net_nd_tx_packet % PKTALIGN;
+}
+
+int ndisc_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len)
+{
+	struct icmp6hdr *icmp =
+	    (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE);
+	struct nd_msg *ndisc = (struct nd_msg *)icmp;
+	uchar neigh_eth_addr[6];
+
+	switch (icmp->icmp6_type) {
+	case IPV6_NDISC_NEIGHBOUR_SOLICITATION:
+		debug("received neighbor solicitation for %pI6c from %pI6c\n",
+		      &ndisc->target, &ip6->saddr);
+		if (ip6_is_our_addr(&ndisc->target) &&
+		    ndisc_has_option(ip6, ND_OPT_SOURCE_LL_ADDR)) {
+			ndisc_extract_enetaddr(ndisc, neigh_eth_addr);
+			ip6_send_na(neigh_eth_addr, &ip6->saddr,
+				    &ndisc->target);
+		}
+		break;
+
+	case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT:
+		/* are we waiting for a reply ? */
+		if (ip6_is_unspecified_addr(&net_nd_sol_packet_ip6))
+			break;
+
+		if ((memcmp(&ndisc->target, &net_nd_rep_packet_ip6,
+			    sizeof(struct in6_addr)) == 0) &&
+		    ndisc_has_option(ip6, ND_OPT_TARGET_LL_ADDR)) {
+			ndisc_extract_enetaddr(ndisc, neigh_eth_addr);
+
+			/* save address for later use */
+			if (!net_nd_packet_mac)
+				memcpy(net_nd_packet_mac, neigh_eth_addr, 7);
+
+			/* modify header, and transmit it */
+			memcpy(((struct ethernet_hdr *)net_nd_tx_packet)->et_dest,
+			       neigh_eth_addr, 6);
+
+			net_send_packet(net_nd_tx_packet,
+					net_nd_tx_packet_size);
+
+			/* no ND request pending now */
+			net_nd_sol_packet_ip6 = net_null_addr_ip6;
+			net_nd_tx_packet_size = 0;
+			net_nd_packet_mac = NULL;
+		}
+		break;
+	default:
+		debug("Unexpected ICMPv6 type 0x%x\n", icmp->icmp6_type);
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/net/net.c b/net/net.c
index aca20e4..1c39acc 100644
--- a/net/net.c
+++ b/net/net.c
@@ -91,6 +91,8 @@
 #include <image.h>
 #include <log.h>
 #include <net.h>
+#include <net6.h>
+#include <ndisc.h>
 #include <net/fastboot.h>
 #include <net/tftp.h>
 #include <net/ncsi.h>
@@ -343,8 +345,17 @@
 
 static int net_init_loop(void)
 {
-	if (eth_get_dev())
+	if (eth_get_dev()) {
 		memcpy(net_ethaddr, eth_get_ethaddr(), 6);
+
+		if (IS_ENABLED(CONFIG_IPV6)) {
+			ip6_make_lladdr(&net_link_local_ip6, net_ethaddr);
+			if (!memcmp(&net_ip6, &net_null_addr_ip6,
+				    sizeof(struct in6_addr)))
+				memcpy(&net_ip6, &net_link_local_ip6,
+				       sizeof(struct in6_addr));
+		}
+	}
 	else
 		/*
 		 * Not ideal, but there's no way to get the actual error, and I
@@ -385,6 +396,7 @@
 				(i + 1) * PKTSIZE_ALIGN;
 		}
 		arp_init();
+		ndisc_init();
 		net_clear_handlers();
 
 		/* Only need to setup buffer pointers once. */
@@ -513,6 +525,11 @@
 			ping_start();
 			break;
 #endif
+#if defined(CONFIG_CMD_PING6)
+		case PING6:
+			ping6_start();
+			break;
+#endif
 #if defined(CONFIG_CMD_NFS) && !defined(CONFIG_SPL_BUILD)
 		case NFS:
 			nfs_start();
@@ -589,6 +606,11 @@
 		if (arp_timeout_check() > 0)
 			time_start = get_timer(0);
 
+		if (IS_ENABLED(CONFIG_IPV6)) {
+			if (use_ip6 && (ndisc_timeout_check() > 0))
+				time_start = get_timer(0);
+		}
+
 		/*
 		 *	Check the ethernet for a new packet.  The ethernet
 		 *	receive routine will process it.
@@ -1244,6 +1266,10 @@
 		rarp_receive(ip, len);
 		break;
 #endif
+#if IS_ENABLED(CONFIG_IPV6)
+	case PROT_IP6:
+		net_ip6_handler(et, (struct ip6_hdr *)ip, len);
+#endif
 	case PROT_IP:
 		debug_cond(DEBUG_NET_PKT, "Got IP\n");
 		/* Before we start poking the header, make sure it is there */
@@ -1413,6 +1439,14 @@
 		}
 		goto common;
 #endif
+#if defined(CONFIG_CMD_PING6)
+	case PING6:
+		if (ip6_is_unspecified_addr(&net_ping_ip6)) {
+			puts("*** ERROR: ping address not given\n");
+			return 1;
+		}
+		goto common;
+#endif
 #if defined(CONFIG_CMD_DNS)
 	case DNS:
 		if (net_dns_server.s_addr == 0) {
@@ -1434,7 +1468,14 @@
 		/* Fall through */
 	case TFTPGET:
 	case TFTPPUT:
-		if (net_server_ip.s_addr == 0 && !is_serverip_in_cmd()) {
+		if (IS_ENABLED(CONFIG_IPV6) && use_ip6) {
+			if (!memcmp(&net_server_ip6, &net_null_addr_ip6,
+				    sizeof(struct in6_addr)) &&
+				    !strchr(net_boot_file_name, '[')) {
+				puts("*** ERROR: `serverip6' not set\n");
+				return 1;
+			}
+		} else if (net_server_ip.s_addr == 0 && !is_serverip_in_cmd()) {
 			puts("*** ERROR: `serverip' not set\n");
 			return 1;
 		}
@@ -1447,7 +1488,13 @@
 	case NETCONS:
 	case FASTBOOT:
 	case TFTPSRV:
-		if (net_ip.s_addr == 0) {
+		if (IS_ENABLED(CONFIG_IPV6) && use_ip6) {
+			if (!memcmp(&net_link_local_ip6, &net_null_addr_ip6,
+				    sizeof(struct in6_addr))) {
+				puts("*** ERROR: `ip6addr` not set\n");
+				return 1;
+			}
+		} else if (net_ip.s_addr == 0) {
 			puts("*** ERROR: `ipaddr' not set\n");
 			return 1;
 		}
diff --git a/net/net6.c b/net/net6.c
new file mode 100644
index 0000000..fdea078
--- /dev/null
+++ b/net/net6.c
@@ -0,0 +1,445 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Allied Telesis Labs NZ
+ * Chris Packham, <judge.packham@gmail.com>
+ *
+ * Copyright (C) 2022 YADRO
+ * Viacheslav Mitrofanov <v.v.mitrofanov@yadro.com>
+ */
+
+/* Simple IPv6 network layer implementation */
+
+#include <common.h>
+#include <env_internal.h>
+#include <malloc.h>
+#include <net.h>
+#include <net6.h>
+#include <ndisc.h>
+
+/* NULL IPv6 address */
+struct in6_addr const net_null_addr_ip6 = ZERO_IPV6_ADDR;
+/* Our gateway's IPv6 address */
+struct in6_addr net_gateway6 = ZERO_IPV6_ADDR;
+/* Our IPv6 addr (0 = unknown) */
+struct in6_addr net_ip6 = ZERO_IPV6_ADDR;
+/* Our link local IPv6 addr (0 = unknown) */
+struct in6_addr net_link_local_ip6 = ZERO_IPV6_ADDR;
+/* set server IPv6 addr (0 = unknown) */
+struct in6_addr net_server_ip6 = ZERO_IPV6_ADDR;
+/* The prefix length of our network */
+u32 net_prefix_length;
+
+bool use_ip6;
+
+static int on_ip6addr(const char *name, const char *value, enum env_op op,
+		      int flags)
+{
+	char *mask;
+	size_t len;
+
+	if (flags & H_PROGRAMMATIC)
+		return 0;
+
+	if (op == env_op_delete) {
+		net_prefix_length = 0;
+		net_copy_ip6(&net_ip6, &net_null_addr_ip6);
+		return 0;
+	}
+
+	mask = strchr(value, '/');
+	len = strlen(value);
+
+	if (mask)
+		net_prefix_length = simple_strtoul(value + len, NULL, 10);
+
+	return string_to_ip6(value, len, &net_ip6);
+}
+
+U_BOOT_ENV_CALLBACK(ip6addr, on_ip6addr);
+
+static int on_gatewayip6(const char *name, const char *value, enum env_op op,
+			 int flags)
+{
+	if (flags & H_PROGRAMMATIC)
+		return 0;
+
+	return string_to_ip6(value, strlen(value), &net_gateway6);
+}
+
+U_BOOT_ENV_CALLBACK(gatewayip6, on_gatewayip6);
+
+static int on_serverip6(const char *name, const char *value, enum env_op op,
+			int flags)
+{
+	if (flags & H_PROGRAMMATIC)
+		return 0;
+
+	return string_to_ip6(value, strlen(value), &net_server_ip6);
+}
+
+U_BOOT_ENV_CALLBACK(serverip6, on_serverip6);
+
+int ip6_is_unspecified_addr(struct in6_addr *addr)
+{
+	return !(addr->s6_addr32[0] | addr->s6_addr32[1] |
+		addr->s6_addr32[2] | addr->s6_addr32[3]);
+}
+
+int ip6_is_our_addr(struct in6_addr *addr)
+{
+	return !memcmp(addr, &net_link_local_ip6, sizeof(struct in6_addr)) ||
+	       !memcmp(addr, &net_ip6, sizeof(struct in6_addr));
+}
+
+void ip6_make_eui(unsigned char eui[8], unsigned char const enetaddr[6])
+{
+	memcpy(eui, enetaddr, 3);
+	memcpy(&eui[5], &enetaddr[3], 3);
+	eui[3] = 0xff;
+	eui[4] = 0xfe;
+	eui[0] ^= 2;		/* "u" bit set to indicate global scope */
+}
+
+void ip6_make_lladdr(struct in6_addr *lladr, unsigned char const enetaddr[6])
+{
+	unsigned char eui[8];
+
+	memset(lladr, 0, sizeof(struct in6_addr));
+	lladr->s6_addr16[0] = htons(IPV6_LINK_LOCAL_PREFIX);
+	ip6_make_eui(eui, enetaddr);
+	memcpy(&lladr->s6_addr[8], eui, 8);
+}
+
+void ip6_make_snma(struct in6_addr *mcast_addr, struct in6_addr *ip6_addr)
+{
+	memset(mcast_addr, 0, sizeof(struct in6_addr));
+	mcast_addr->s6_addr[0] = 0xff;
+	mcast_addr->s6_addr[1] = IPV6_ADDRSCOPE_LINK;
+	mcast_addr->s6_addr[11] = 0x01;
+	mcast_addr->s6_addr[12] = 0xff;
+	mcast_addr->s6_addr[13] = ip6_addr->s6_addr[13];
+	mcast_addr->s6_addr[14] = ip6_addr->s6_addr[14];
+	mcast_addr->s6_addr[15] = ip6_addr->s6_addr[15];
+}
+
+void
+ip6_make_mult_ethdstaddr(unsigned char enetaddr[6], struct in6_addr *mcast_addr)
+{
+	enetaddr[0] = 0x33;
+	enetaddr[1] = 0x33;
+	memcpy(&enetaddr[2], &mcast_addr->s6_addr[12], 4);
+}
+
+int
+ip6_addr_in_subnet(struct in6_addr *our_addr, struct in6_addr *neigh_addr,
+		   u32 plen)
+{
+	__be32 *addr_dwords;
+	__be32 *neigh_dwords;
+
+	addr_dwords = our_addr->s6_addr32;
+	neigh_dwords = neigh_addr->s6_addr32;
+
+	while (plen > 32) {
+		if (*addr_dwords++ != *neigh_dwords++)
+			return 0;
+
+		plen -= 32;
+	}
+
+	/* Check any remaining bits */
+	if (plen > 0) {
+		if ((*addr_dwords >> (32 - plen)) !=
+		    (*neigh_dwords >> (32 - plen))) {
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+static inline unsigned int csum_fold(unsigned int sum)
+{
+	sum = (sum & 0xffff) + (sum >> 16);
+	sum = (sum & 0xffff) + (sum >> 16);
+
+	/* Opaque moment. If reverse it to zero it will not be checked on
+	 * receiver's side. It leads to bad negibour advertisement.
+	 */
+	if (sum == 0xffff)
+		return sum;
+
+	return ~sum;
+}
+
+static inline unsigned short from32to16(unsigned int x)
+{
+	/* add up 16-bit and 16-bit for 16+c bit */
+	x = (x & 0xffff) + (x >> 16);
+	/* add up carry.. */
+	x = (x & 0xffff) + (x >> 16);
+	return x;
+}
+
+static u32 csum_do_csum(const u8 *buff, int len)
+{
+	int odd;
+	unsigned int result = 0;
+
+	if (len <= 0)
+		goto out;
+	odd = 1 & (unsigned long)buff;
+	if (odd) {
+#ifdef __LITTLE_ENDIAN
+		result += (*buff << 8);
+#else
+		result = *buff;
+#endif
+		len--;
+		buff++;
+	}
+	if (len >= 2) {
+		if (2 & (unsigned long)buff) {
+			result += *(unsigned short *)buff;
+			len -= 2;
+			buff += 2;
+		}
+		if (len >= 4) {
+			const unsigned char *end = buff + ((u32)len & ~3);
+			unsigned int carry = 0;
+
+			do {
+				unsigned int w = *(unsigned int *)buff;
+
+				buff += 4;
+				result += carry;
+				result += w;
+				carry = (w > result);
+			} while (buff < end);
+			result += carry;
+			result = (result & 0xffff) + (result >> 16);
+		}
+		if (len & 2) {
+			result += *(unsigned short *)buff;
+			buff += 2;
+		}
+	}
+	if (len & 1)
+#ifdef __LITTLE_ENDIAN
+		result += *buff;
+#else
+		result += (*buff << 8);
+#endif
+	result = from32to16(result);
+	if (odd)
+		result = ((result >> 8) & 0xff) | ((result & 0xff) << 8);
+out:
+	return result;
+}
+
+unsigned int csum_partial(const unsigned char *buff, int len, unsigned int sum)
+{
+	unsigned int result = csum_do_csum(buff, len);
+
+	/* add in old sum, and carry.. */
+	result += sum;
+	/* 16+c bits -> 16 bits */
+	result = (result & 0xffff) + (result >> 16);
+	return result;
+}
+
+unsigned short int
+csum_ipv6_magic(struct in6_addr *saddr, struct in6_addr *daddr, u16 len,
+		unsigned short proto, unsigned int csum)
+{
+	int carry;
+	u32 ulen;
+	u32 uproto;
+	u32 sum = csum;
+
+	sum += saddr->s6_addr32[0];
+	carry = (sum < saddr->s6_addr32[0]);
+	sum += carry;
+
+	sum += saddr->s6_addr32[1];
+	carry = (sum < saddr->s6_addr32[1]);
+	sum += carry;
+
+	sum += saddr->s6_addr32[2];
+	carry = (sum < saddr->s6_addr32[2]);
+	sum += carry;
+
+	sum += saddr->s6_addr32[3];
+	carry = (sum < saddr->s6_addr32[3]);
+	sum += carry;
+
+	sum += daddr->s6_addr32[0];
+	carry = (sum < daddr->s6_addr32[0]);
+	sum += carry;
+
+	sum += daddr->s6_addr32[1];
+	carry = (sum < daddr->s6_addr32[1]);
+	sum += carry;
+
+	sum += daddr->s6_addr32[2];
+	carry = (sum < daddr->s6_addr32[2]);
+	sum += carry;
+
+	sum += daddr->s6_addr32[3];
+	carry = (sum < daddr->s6_addr32[3]);
+	sum += carry;
+
+	ulen = htonl((u32)len);
+	sum += ulen;
+	carry = (sum < ulen);
+	sum += carry;
+
+	uproto = htonl(proto);
+	sum += uproto;
+	carry = (sum < uproto);
+	sum += carry;
+
+	return csum_fold(sum);
+}
+
+int ip6_add_hdr(uchar *xip, struct in6_addr *src, struct in6_addr *dest,
+		int nextheader, int hoplimit, int payload_len)
+{
+	struct ip6_hdr *ip6 = (struct ip6_hdr *)xip;
+
+	ip6->version = 6;
+	ip6->priority = 0;
+	ip6->flow_lbl[0] = 0;
+	ip6->flow_lbl[1] = 0;
+	ip6->flow_lbl[2] = 0;
+	ip6->payload_len = htons(payload_len);
+	ip6->nexthdr = nextheader;
+	ip6->hop_limit = hoplimit;
+	net_copy_ip6(&ip6->saddr, src);
+	net_copy_ip6(&ip6->daddr, dest);
+
+	return sizeof(struct ip6_hdr);
+}
+
+int net_send_udp_packet6(uchar *ether, struct in6_addr *dest, int dport,
+			 int sport, int len)
+{
+	uchar *pkt;
+	struct udp_hdr *udp;
+	u16 csum_p;
+
+	udp = (struct udp_hdr *)((uchar *)net_tx_packet + net_eth_hdr_size() +
+			IP6_HDR_SIZE);
+
+	udp->udp_dst = htons(dport);
+	udp->udp_src = htons(sport);
+	udp->udp_len = htons(len + UDP_HDR_SIZE);
+
+	/* checksum */
+	udp->udp_xsum = 0;
+	csum_p = csum_partial((u8 *)udp, len + UDP_HDR_SIZE, 0);
+	udp->udp_xsum = csum_ipv6_magic(&net_ip6, dest, len + UDP_HDR_SIZE,
+					IPPROTO_UDP, csum_p);
+
+	/* if MAC address was not discovered yet, save the packet and do
+	 * neighbour discovery
+	 */
+	if (!memcmp(ether, net_null_ethaddr, 6)) {
+		net_copy_ip6(&net_nd_sol_packet_ip6, dest);
+		net_nd_packet_mac = ether;
+
+		pkt = net_nd_tx_packet;
+		pkt += net_set_ether(pkt, net_nd_packet_mac, PROT_IP6);
+		pkt += ip6_add_hdr(pkt, &net_ip6, dest, IPPROTO_UDP, 64,
+				len + UDP_HDR_SIZE);
+		memcpy(pkt, (uchar *)udp, len + UDP_HDR_SIZE);
+
+		/* size of the waiting packet */
+		net_nd_tx_packet_size = (pkt - net_nd_tx_packet) +
+			UDP_HDR_SIZE + len;
+
+		/* and do the neighbor solicitation */
+		net_nd_try = 1;
+		net_nd_timer_start = get_timer(0);
+		ndisc_request();
+		return 1;	/* waiting */
+	}
+
+	pkt = (uchar *)net_tx_packet;
+	pkt += net_set_ether(pkt, ether, PROT_IP6);
+	pkt += ip6_add_hdr(pkt, &net_ip6, dest, IPPROTO_UDP, 64,
+			len + UDP_HDR_SIZE);
+	(void)eth_send(net_tx_packet, pkt - net_tx_packet + UDP_HDR_SIZE + len);
+
+	return 0;	/* transmitted */
+}
+
+int net_ip6_handler(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len)
+{
+	struct in_addr zero_ip = {.s_addr = 0 };
+	struct icmp6hdr *icmp;
+	struct udp_hdr *udp;
+	u16 csum;
+	u16 csum_p;
+	u16 hlen;
+
+	if (len < IP6_HDR_SIZE)
+		return -EINVAL;
+
+	if (ip6->version != 6)
+		return -EINVAL;
+
+	switch (ip6->nexthdr) {
+	case PROT_ICMPV6:
+		icmp = (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE);
+		csum = icmp->icmp6_cksum;
+		hlen = ntohs(ip6->payload_len);
+		icmp->icmp6_cksum = 0;
+		/* checksum */
+		csum_p = csum_partial((u8 *)icmp, hlen, 0);
+		icmp->icmp6_cksum = csum_ipv6_magic(&ip6->saddr, &ip6->daddr,
+						    hlen, PROT_ICMPV6, csum_p);
+
+		if (icmp->icmp6_cksum != csum)
+			return -EINVAL;
+
+		switch (icmp->icmp6_type) {
+		case IPV6_ICMP_ECHO_REQUEST:
+		case IPV6_ICMP_ECHO_REPLY:
+			ping6_receive(et, ip6, len);
+			break;
+		case IPV6_NDISC_NEIGHBOUR_SOLICITATION:
+		case IPV6_NDISC_NEIGHBOUR_ADVERTISEMENT:
+			ndisc_receive(et, ip6, len);
+			break;
+		default:
+			break;
+		}
+		break;
+	case IPPROTO_UDP:
+		udp = (struct udp_hdr *)(((uchar *)ip6) + IP6_HDR_SIZE);
+		csum = udp->udp_xsum;
+		hlen = ntohs(ip6->payload_len);
+		udp->udp_xsum = 0;
+		/* checksum */
+		csum_p = csum_partial((u8 *)udp, hlen, 0);
+		udp->udp_xsum = csum_ipv6_magic(&ip6->saddr, &ip6->daddr,
+						hlen, IPPROTO_UDP, csum_p);
+
+		if (csum != udp->udp_xsum)
+			return -EINVAL;
+
+		/* IP header OK. Pass the packet to the current handler. */
+		net_get_udp_handler()((uchar *)ip6 + IP6_HDR_SIZE +
+					UDP_HDR_SIZE,
+				ntohs(udp->udp_dst),
+				zero_ip,
+				ntohs(udp->udp_src),
+				ntohs(udp->udp_len) - 8);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
diff --git a/net/ping6.c b/net/ping6.c
new file mode 100644
index 0000000..4882a17
--- /dev/null
+++ b/net/ping6.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Allied Telesis Labs NZ
+ * Chris Packham, <judge.packham@gmail.com>
+ *
+ * Copyright (C) 2022 YADRO
+ * Viacheslav Mitrofanov <v.v.mitrofanov@yadro.com>
+ */
+
+/* Simple ping6 implementation */
+
+#include <common.h>
+#include <net.h>
+#include <net6.h>
+#include "ndisc.h"
+
+static ushort seq_no;
+
+/* the ipv6 address to ping */
+struct in6_addr net_ping_ip6;
+
+int
+ip6_make_ping(uchar *eth_dst_addr, struct in6_addr *neigh_addr, uchar *pkt)
+{
+	struct echo_msg *msg;
+	u16 len;
+	u16 csum_p;
+	uchar *pkt_old = pkt;
+
+	len = sizeof(struct echo_msg);
+
+	pkt += net_set_ether(pkt, eth_dst_addr, PROT_IP6);
+	pkt += ip6_add_hdr(pkt, &net_ip6, neigh_addr, PROT_ICMPV6,
+			   IPV6_NDISC_HOPLIMIT, len);
+
+	/* ICMPv6 - Echo */
+	msg = (struct echo_msg *)pkt;
+	msg->icmph.icmp6_type = IPV6_ICMP_ECHO_REQUEST;
+	msg->icmph.icmp6_code = 0;
+	msg->icmph.icmp6_cksum = 0;
+	msg->icmph.icmp6_identifier = 0;
+	msg->icmph.icmp6_sequence = htons(seq_no++);
+	msg->id = msg->icmph.icmp6_identifier;	/* these seem redundant */
+	msg->sequence = msg->icmph.icmp6_sequence;
+
+	/* checksum */
+	csum_p = csum_partial((u8 *)msg, len, 0);
+	msg->icmph.icmp6_cksum = csum_ipv6_magic(&net_ip6, neigh_addr, len,
+						 PROT_ICMPV6, csum_p);
+
+	pkt += len;
+
+	return pkt - pkt_old;
+}
+
+int ping6_send(void)
+{
+	uchar *pkt;
+	static uchar mac[6];
+
+	/* always send neighbor solicit */
+
+	memcpy(mac, net_null_ethaddr, 6);
+
+	net_nd_sol_packet_ip6 = net_ping_ip6;
+	net_nd_packet_mac = mac;
+
+	pkt = net_nd_tx_packet;
+	pkt += ip6_make_ping(mac, &net_ping_ip6, pkt);
+
+	/* size of the waiting packet */
+	net_nd_tx_packet_size = (pkt - net_nd_tx_packet);
+
+	/* and do the ARP request */
+	net_nd_try = 1;
+	net_nd_timer_start = get_timer(0);
+	ndisc_request();
+	return 1;		/* waiting */
+}
+
+static void ping6_timeout(void)
+{
+	eth_halt();
+	net_set_state(NETLOOP_FAIL);	/* we did not get the reply */
+}
+
+void ping6_start(void)
+{
+	printf("Using %s device\n", eth_get_name());
+	net_set_timeout_handler(10000UL, ping6_timeout);
+
+	ping6_send();
+}
+
+int ping6_receive(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len)
+{
+	struct icmp6hdr *icmp =
+	    (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE);
+	struct in6_addr src_ip;
+
+	switch (icmp->icmp6_type) {
+	case IPV6_ICMP_ECHO_REPLY:
+		src_ip = ip6->saddr;
+		if (memcmp(&net_ping_ip6, &src_ip, sizeof(struct in6_addr)))
+			return -EINVAL;
+		net_set_state(NETLOOP_SUCCESS);
+		break;
+	case IPV6_ICMP_ECHO_REQUEST:
+		/* ignore for now.... */
+		debug("Got ICMPv6 ECHO REQUEST from %pI6c\n", &ip6->saddr);
+		return -EINVAL;
+	default:
+		debug("Unexpected ICMPv6 type 0x%x\n", icmp->icmp6_type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
diff --git a/net/tftp.c b/net/tftp.c
index 39421f8..c780c33 100644
--- a/net/tftp.c
+++ b/net/tftp.c
@@ -15,6 +15,7 @@
 #include <log.h>
 #include <mapmem.h>
 #include <net.h>
+#include <net6.h>
 #include <asm/global_data.h>
 #include <net/tftp.h>
 #include "bootp.h"
@@ -41,6 +42,7 @@
 static ulong timeout_ms = TIMEOUT;
 static int timeout_count_max = (CONFIG_NET_RETRY_COUNT * 2);
 static ulong time_start;   /* Record time we started tftp */
+static struct in6_addr tftp_remote_ip6;
 
 /*
  * These globals govern the timeout behavior when attempting a connection to a
@@ -116,6 +118,7 @@
 
 /* default TFTP block size */
 #define TFTP_BLOCK_SIZE		512
+#define TFTP_MTU_BLOCKSIZE6 (CONFIG_TFTP_BLOCKSIZE - 20)
 /* sequence number is 16 bit */
 #define TFTP_SEQUENCE_SIZE	((ulong)(1<<16))
 
@@ -320,7 +323,11 @@
 	 *	We will always be sending some sort of packet, so
 	 *	cobble together the packet headers now.
 	 */
-	pkt = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE;
+	if (IS_ENABLED(CONFIG_IPV6) && use_ip6)
+		pkt = net_tx_packet + net_eth_hdr_size() +
+		      IP6_HDR_SIZE + UDP_HDR_SIZE;
+	else
+		pkt = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE;
 
 	switch (tftp_state) {
 	case STATE_SEND_RRQ:
@@ -422,8 +429,14 @@
 		break;
 	}
 
-	net_send_udp_packet(net_server_ethaddr, tftp_remote_ip,
-			    tftp_remote_port, tftp_our_port, len);
+	if (IS_ENABLED(CONFIG_IPV6) && use_ip6)
+		net_send_udp_packet6(net_server_ethaddr,
+				     &tftp_remote_ip6,
+				     tftp_remote_port,
+				     tftp_our_port, len);
+	else
+		net_send_udp_packet(net_server_ethaddr, tftp_remote_ip,
+				    tftp_remote_port, tftp_our_port, len);
 
 	if (err_pkt)
 		net_set_state(NETLOOP_FAIL);
@@ -797,6 +810,9 @@
 	debug("TFTP blocksize = %i, TFTP windowsize = %d timeout = %ld ms\n",
 	      tftp_block_size_option, tftp_window_size_option, timeout_ms);
 
+	if (IS_ENABLED(CONFIG_IPV6))
+		tftp_remote_ip6 = net_server_ip6;
+
 	tftp_remote_ip = net_server_ip;
 	if (!net_parse_bootfile(&tftp_remote_ip, tftp_filename, MAX_LEN)) {
 		sprintf(default_filename, "%02X%02X%02X%02X.img",
@@ -812,17 +828,49 @@
 		       tftp_filename);
 	}
 
+	if (IS_ENABLED(CONFIG_IPV6)) {
+		if (use_ip6) {
+			char *s, *e;
+			size_t len;
+
+			s = strchr(net_boot_file_name, '[');
+			e = strchr(net_boot_file_name, ']');
+			len = e - s;
+			if (s && e) {
+				string_to_ip6(s + 1, len, &tftp_remote_ip6);
+				strlcpy(tftp_filename, e + 2, MAX_LEN);
+			} else {
+				strlcpy(tftp_filename, net_boot_file_name, MAX_LEN);
+				tftp_filename[MAX_LEN - 1] = 0;
+			}
+		}
+	}
+
 	printf("Using %s device\n", eth_get_name());
-	printf("TFTP %s server %pI4; our IP address is %pI4",
+
+	if (IS_ENABLED(CONFIG_IPV6) && use_ip6) {
+		printf("TFTP from server %pI6c; our IP address is %pI6c",
+		       &tftp_remote_ip6, &net_ip6);
+
+		if (tftp_block_size_option > TFTP_MTU_BLOCKSIZE6)
+			tftp_block_size_option = TFTP_MTU_BLOCKSIZE6;
+	} else {
+		printf("TFTP %s server %pI4; our IP address is %pI4",
 #ifdef CONFIG_CMD_TFTPPUT
 	       protocol == TFTPPUT ? "to" : "from",
 #else
 	       "from",
 #endif
 	       &tftp_remote_ip, &net_ip);
+	}
 
 	/* Check if we need to send across this subnet */
-	if (net_gateway.s_addr && net_netmask.s_addr) {
+	if (IS_ENABLED(CONFIG_IPV6) && use_ip6) {
+		if (!ip6_addr_in_subnet(&net_ip6, &tftp_remote_ip6,
+					net_prefix_length))
+			printf("; sending through gateway %pI6c",
+			       &net_gateway6);
+	} else if (net_gateway.s_addr && net_netmask.s_addr) {
 		struct in_addr our_net;
 		struct in_addr remote_net;
 
diff --git a/test/dm/eth.c b/test/dm/eth.c
index 5437f9e..ebf01d8 100644
--- a/test/dm/eth.c
+++ b/test/dm/eth.c
@@ -13,6 +13,7 @@
 #include <log.h>
 #include <malloc.h>
 #include <net.h>
+#include <net6.h>
 #include <asm/eth.h>
 #include <dm/test.h>
 #include <dm/device-internal.h>
@@ -22,6 +23,152 @@
 
 #define DM_TEST_ETH_NUM		4
 
+#if IS_ENABLED(CONFIG_IPV6)
+static int dm_test_string_to_ip6(struct unit_test_state *uts)
+{
+	char *str;
+	struct test_ip6_pair {
+		char 		*string_addr;
+		struct in6_addr ip6_addr;
+	};
+
+	struct in6_addr ip6 = {0};
+
+	/* Correct statements */
+	struct test_ip6_pair test_suite[] = {
+		{"2001:db8::0:1234:1", {.s6_addr32[0] = 0xb80d0120,
+					.s6_addr32[1] = 0x00000000,
+					.s6_addr32[2] = 0x00000000,
+					.s6_addr32[3] = 0x01003412}},
+		{"2001:0db8:0000:0000:0000:0000:1234:0001",
+				       {.s6_addr32[0] = 0xb80d0120,
+					.s6_addr32[1] = 0x00000000,
+					.s6_addr32[2] = 0x00000000,
+					.s6_addr32[3] = 0x01003412}},
+		{"::1", 	       {.s6_addr32[0] = 0x00000000,
+					.s6_addr32[1] = 0x00000000,
+					.s6_addr32[2] = 0x00000000,
+					.s6_addr32[3] = 0x01000000}},
+		{"::ffff:192.168.1.1", {.s6_addr32[0] = 0x00000000,
+					.s6_addr32[1] = 0x00000000,
+					.s6_addr32[2] = 0xffff0000,
+					.s6_addr32[3] = 0x0101a8c0}},
+	};
+
+	for (int i = 0; i < ARRAY_SIZE(test_suite); ++i) {
+		ut_assertok(string_to_ip6(test_suite[i].string_addr,
+			    strlen(test_suite[i].string_addr), &ip6));
+		ut_asserteq_mem(&ip6, &test_suite[i].ip6_addr,
+				sizeof(struct in6_addr));
+	}
+
+	/* Incorrect statements */
+	str = "hello:world";
+	ut_assertok(!string_to_ip6(str, strlen(str), &ip6));
+	str = "2001:db8::0::0";
+	ut_assertok(!string_to_ip6(str, strlen(str), &ip6));
+	str = "2001:db8:192.168.1.1::1";
+	ut_assertok(!string_to_ip6(str, strlen(str), &ip6));
+	str = "192.168.1.1";
+	ut_assertok(!string_to_ip6(str, strlen(str), &ip6));
+
+	return 0;
+}
+DM_TEST(dm_test_string_to_ip6, 0);
+
+static int dm_test_csum_ipv6_magic(struct unit_test_state *uts)
+{
+	unsigned short csum = 0xbeef;
+	/* Predefined correct parameters */
+	unsigned short correct_csum = 0xd8ac;
+	struct in6_addr saddr = {.s6_addr32[0] = 0x000080fe,
+				 .s6_addr32[1] = 0x00000000,
+				 .s6_addr32[2] = 0xffe9f242,
+				 .s6_addr32[3] = 0xe8f66dfe};
+	struct in6_addr daddr = {.s6_addr32[0] = 0x000080fe,
+				 .s6_addr32[1] = 0x00000000,
+				 .s6_addr32[2] = 0xffd5b372,
+				 .s6_addr32[3] = 0x3ef692fe};
+	u16 len = 1460;
+	unsigned short proto = 17;
+	unsigned int head_csum = 0x91f0;
+
+	csum = csum_ipv6_magic(&saddr, &daddr, len, proto, head_csum);
+	ut_asserteq(csum, correct_csum);
+
+	/* Broke a parameter */
+	proto--;
+	csum = csum_ipv6_magic(&saddr, &daddr, len, proto, head_csum);
+	ut_assert(csum != correct_csum);
+
+	return 0;
+}
+DM_TEST(dm_test_csum_ipv6_magic, 0);
+
+static int dm_test_ip6_addr_in_subnet(struct unit_test_state *uts)
+{
+	struct in6_addr our = {.s6_addr32[0] = 0x000080fe,
+				 .s6_addr32[1] = 0x00000000,
+				 .s6_addr32[2] = 0xffe9f242,
+				 .s6_addr32[3] = 0xe8f66dfe};
+	struct in6_addr neigh1 = {.s6_addr32[0] = 0x000080fe,
+				 .s6_addr32[1] = 0x00000000,
+				 .s6_addr32[2] = 0xffd5b372,
+				 .s6_addr32[3] = 0x3ef692fe};
+	struct in6_addr neigh2 = {.s6_addr32[0] = 0x60480120,
+				 .s6_addr32[1] = 0x00006048,
+				 .s6_addr32[2] = 0x00000000,
+				 .s6_addr32[3] = 0x00008888};
+
+	/* in */
+	ut_assert(ip6_addr_in_subnet(&our, &neigh1, 64));
+	/* outside */
+	ut_assert(!ip6_addr_in_subnet(&our, &neigh2, 64));
+	ut_assert(!ip6_addr_in_subnet(&our, &neigh1, 128));
+
+	return 0;
+}
+DM_TEST(dm_test_ip6_addr_in_subnet, 0);
+
+static int dm_test_ip6_make_snma(struct unit_test_state *uts)
+{
+	struct in6_addr mult = {0};
+	struct in6_addr correct_addr = {
+				 .s6_addr32[0] = 0x000002ff,
+				 .s6_addr32[1] = 0x00000000,
+				 .s6_addr32[2] = 0x01000000,
+				 .s6_addr32[3] = 0xe8f66dff};
+	struct in6_addr addr = { .s6_addr32[0] = 0x000080fe,
+				 .s6_addr32[1] = 0x00000000,
+				 .s6_addr32[2] = 0xffe9f242,
+				 .s6_addr32[3] = 0xe8f66dfe};
+
+	ip6_make_snma(&mult, &addr);
+	ut_asserteq_mem(&mult, &correct_addr, sizeof(struct in6_addr));
+
+	return 0;
+}
+DM_TEST(dm_test_ip6_make_snma, 0);
+
+static int dm_test_ip6_make_lladdr(struct unit_test_state *uts)
+{
+	struct in6_addr generated_lladdr = {0};
+	struct in6_addr correct_lladdr = {
+				 .s6_addr32[0] = 0x000080fe,
+				 .s6_addr32[1] = 0x00000000,
+				 .s6_addr32[2] = 0xffabf33a,
+				 .s6_addr32[3] = 0xfbb352fe};
+	const unsigned char mac[6] = {0x38, 0xf3, 0xab, 0x52, 0xb3, 0xfb};
+
+	ip6_make_lladdr(&generated_lladdr, mac);
+	ut_asserteq_mem(&generated_lladdr, &correct_lladdr,
+			sizeof(struct in6_addr));
+
+	return 0;
+}
+DM_TEST(dm_test_ip6_make_lladdr, UT_TESTF_SCAN_FDT);
+#endif
+
 static int dm_test_eth(struct unit_test_state *uts)
 {
 	net_ping_ip = string_to_ip("1.1.2.2");