All of lore.kernel.org
 help / color / mirror / Atom feed
From: DH at synoia.com <DH@synoia.com>
To: u-boot@lists.denx.de
Subject: [U-Boot] [PATCH v10 3/3] Adding wget
Date: Sat, 14 Apr 2018 16:43:36 -0700	[thread overview]
Message-ID: <20180414234336.26636-4-DH@synoia.com> (raw)
In-Reply-To: <20180414234336.26636-1-DH@synoia.com>

From: Duncan Hare <DuncanCHare@yahoo.com>

Why http and wget:

HTTP is the most efficient file retrieval protocol in common
use. The client send a single request, after TCP connection,
to receive a file of any length.

WGET is the application which implements http file transfer
outside browsers as a file transfer protocol. Versions of
wget exists on many operating systems.
END

Signed-off-by: Duncan Hare <DuncanCHare@yahoo.com>
---

Changes in v10: None

 cmd/Kconfig        |   5 +
 cmd/net.c          |  13 ++
 include/net.h      |  16 +-
 include/net/wget.h |  17 +++
 net/Kconfig        |   7 +-
 net/Makefile       |   1 +
 net/wget.c         | 420 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 470 insertions(+), 9 deletions(-)
 create mode 100644 include/net/wget.h
 create mode 100644 net/wget.c

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 136836d146..1113d69950 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1094,6 +1094,11 @@ config CMD_ETHSW
 	  operations such as enabling / disabling a port and
 	  viewing/maintaining the filtering database (FDB)
 
+config CMD_WGET
+	bool "wget"
+	select TCP
+	help
+	  Download a kernel, or other files, from a web server over TCP.
 endif
 
 endmenu
diff --git a/cmd/net.c b/cmd/net.c
index d7c776aacf..6a7a51f357 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -110,6 +110,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 e29d804a23..ec44bf2bec 100644
--- a/include/net.h
+++ b/include/net.h
@@ -15,10 +15,14 @@
 #include <asm/cache.h>
 #include <asm/byteorder.h>	/* for nton* / ntoh* stuff */
 
-#define DEBUG_LL_STATE  0	/* Link local state machine changes */
-#define DEBUG_DEV_PKT   0	/* Packets or info directed to the device */
+#define DEBUG_LL_STATE  0	/* Link local state machine changes        */
+#define DEBUG_DEV_PKT   0	/* Packets or info directed to the device  */
 #define DEBUG_NET_PKT   0	/* Packets on info on the network at large */
-#define DEBUG_INT_STATE 0	/* Internal network state changes */
+#define DEBUG_INT_STATE 0	/* Internal network state change           */
+
+#if defined(CONFIG_TCP)
+#define CONFIG_SYS_RX_ETH_BUFFER 12     /* For TCP */
+#endif
 
 /*
  *	The number of receive packet buffers, and the required packet buffer
@@ -31,10 +35,6 @@
  *	data, the sending TCP will stop sending.
  */
 
-#if defined(CONFIG_TCP)
-#define CONFIG_SYS_RX_ETH_BUFFER 12	/* For TCP */
-#endif
-
 #ifdef CONFIG_SYS_RX_ETH_BUFFER
 # define PKTBUFSRX	CONFIG_SYS_RX_ETH_BUFFER
 #else
@@ -362,8 +362,8 @@ 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		*/
-#define IPPROTO_TCP	 6	/* Transmission Control Protocol        */
 
 /*
  *	Internet Protocol (IP) header.
diff --git a/include/net/wget.h b/include/net/wget.h
new file mode 100644
index 0000000000..dce16c573d
--- /dev/null
+++ b/include/net/wget.h
@@ -0,0 +1,17 @@
+/*
+ * 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/Kconfig b/net/Kconfig
index cc6dd83fc4..c973def3b8 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -36,7 +36,12 @@ config NET_TFTP_VARS
 config TCP
 	bool "Include Subset TCP stack for wget"
 	help
-	  TCP protocol support for wget.
+	  Selecting TCP protocol support adds TCP for receiving
+	  streams of data. The TCP packets are presented
+	  as received (possibly out of order). The application
+	  receiving the TCP stream places the date by sequence number
+	  as an offset from the kernel address. TCP transmission is
+	  not supported.
 
 config BOOTP_BOOTPATH
 	bool "Enable BOOTP BOOTPATH"
diff --git a/net/Makefile b/net/Makefile
index 25729ebeeb..f83df5b728 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_CMD_RARP) += rarp.o
 obj-$(CONFIG_CMD_SNTP) += sntp.o
 obj-$(CONFIG_CMD_NET)  += tftp.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)
 # and this is intentional usage.
diff --git a/net/wget.c b/net/wget.c
new file mode 100644
index 0000000000..5429a1f7b1
--- /dev/null
+++ b/net/wget.c
@@ -0,0 +1,420 @@
+/*
+ * WGET/HTTP support driver based on U-BOOT's nfs.c
+ * Copyright Duncan Hare <dh@synoia.com> 2017
+ * SPDX-License-Identifier:GPL-2.0+
+ */
+
+#include <common.h>
+#include <command.h>
+#include <mapmem.h>
+#include <net.h>
+#include <net/wget.h>
+#include <net/tcp.h>
+
+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 r_action;
+static unsigned int r_tcp_ack_num;
+static unsigned int r_tcp_seq_num;
+static int r_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                  = r_action;
+	unsigned int tcp_ack_num   = r_tcp_ack_num;
+	unsigned int tcp_seq_num   = r_tcp_seq_num;
+	int len                    = r_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)
+{
+	r_action        = action;
+	r_tcp_ack_num   = tcp_ack_num;
+	r_tcp_seq_num   = tcp_seq_num;
+	r_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;
+		tcp_print_buffer(pkt, i, i, -1, 0);
+
+		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 {
+				tcp_print_buffer(pkt, len, len, -1, 0);
+				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);
+		} else {
+			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) {
+		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 in case the server ip has changed */
+	memset(net_server_ethaddr, 0, 6);
+
+	wget_send(TCP_SYN, 0, 0, 0);
+}
-- 
2.11.0

  parent reply	other threads:[~2018-04-14 23:43 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-04-14 23:43 [U-Boot] [PATCH v10 0/3] Why netboot: DH at synoia.com
2018-04-14 23:43 ` [U-Boot] [PATCH v10 1/3] Adding TCP and wget into u-boot DH at synoia.com
2018-04-30 23:44   ` Joe Hershberger
2018-04-14 23:43 ` [U-Boot] [PATCH v10 2/3] Adding TCP DH at synoia.com
2018-05-01  1:44   ` Joe Hershberger
2018-04-14 23:43 ` DH at synoia.com [this message]
2018-04-17 15:10   ` [U-Boot] [PATCH v10 3/3] Adding wget Simon Glass
2018-04-18 15:50     ` Duncan Hare
     [not found]     ` <217820715.1487025.1524002336830@mail.yahoo.com>
     [not found]       ` <CAPnjgZ3yNbMVJCbNQVDxxQ9RaX1pujxKSopk=s1MNUyb=oAyiQ@mail.gmail.com>
2018-04-23  3:22         ` Duncan Hare
2018-04-25  5:01           ` Simon Glass
2018-04-25 14:33             ` Duncan Hare
2018-04-25 23:44               ` Simon Glass
2018-04-25 23:52                 ` Duncan Hare
2018-05-01  1:54                   ` Joe Hershberger
2018-05-13 21:05                     ` [U-Boot] net: " Duncan Hare
2018-05-13 22:00                       ` Simon Glass
2018-05-14 15:26                         ` Duncan Hare
2018-05-14 19:51                           ` Simon Glass
2018-05-01  1:50     ` [U-Boot] " Joe Hershberger
2018-05-01  2:18   ` Joe Hershberger
2018-05-01  1:57 ` [U-Boot] [PATCH v10 0/3] Why netboot: Joe Hershberger
2018-05-01 21:29   ` Joe Hershberger

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180414234336.26636-4-DH@synoia.com \
    --to=dh@synoia.com \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.