From: Sascha Hauer <s.hauer@pengutronix.de>
To: Barebox List <barebox@lists.infradead.org>
Cc: "Edmund Henniges" <eh@emlix.com>, "Daniel Glöckner" <dg@emlix.com>
Subject: [PATCH 18/19] fastboot net: implement fastboot over UDP
Date: Wed, 20 May 2020 11:44:02 +0200 [thread overview]
Message-ID: <20200520094403.12651-19-s.hauer@pengutronix.de> (raw)
In-Reply-To: <20200520094403.12651-1-s.hauer@pengutronix.de>
From: Edmund Henniges <eh@emlix.com>
This implements the UDP variant of the fastboot protocol. The only way to
start the service for now is to compile with CONFIG_FASTBOOT_NET_ON_BOOT.
The service will bind to the network interface that provides the IPv4
gateway.
Sending an OKAY packet before performing a restart is necessary since
contrary to USB the host will not notice when a UDP server disappears.
Signed-off-by: Edmund Henniges <eh@emlix.com>
Signed-off-by: Daniel Glöckner <dg@emlix.com>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
common/fastboot.c | 3 +
include/fastboot.h | 1 +
include/fastboot_net.h | 12 +
net/Kconfig | 18 ++
net/Makefile | 1 +
net/fastboot.c | 494 +++++++++++++++++++++++++++++++++++++++++
6 files changed, 529 insertions(+)
create mode 100644 include/fastboot_net.h
create mode 100644 net/fastboot.c
diff --git a/common/fastboot.c b/common/fastboot.c
index 7a2f31a3e2..cd8647c338 100644
--- a/common/fastboot.c
+++ b/common/fastboot.c
@@ -250,6 +250,7 @@ static char *fastboot_msg[] = {
[FASTBOOT_MSG_FAIL] = "FAIL",
[FASTBOOT_MSG_INFO] = "INFO",
[FASTBOOT_MSG_DATA] = "DATA",
+ [FASTBOOT_MSG_NONE] = "",
};
int fastboot_tx_print(struct fastboot *fb, enum fastboot_msg_type type,
@@ -278,6 +279,7 @@ int fastboot_tx_print(struct fastboot *fb, enum fastboot_msg_type type,
case FASTBOOT_MSG_INFO:
pr_info("%pV\n", &vaf);
break;
+ case FASTBOOT_MSG_NONE:
case FASTBOOT_MSG_DATA:
break;
}
@@ -292,6 +294,7 @@ int fastboot_tx_print(struct fastboot *fb, enum fastboot_msg_type type,
static void cb_reboot(struct fastboot *fb, const char *cmd)
{
+ fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, "");
restart_machine();
}
diff --git a/include/fastboot.h b/include/fastboot.h
index 88ec478499..5c3e2894df 100644
--- a/include/fastboot.h
+++ b/include/fastboot.h
@@ -52,6 +52,7 @@ enum fastboot_msg_type {
FASTBOOT_MSG_FAIL,
FASTBOOT_MSG_INFO,
FASTBOOT_MSG_DATA,
+ FASTBOOT_MSG_NONE,
};
extern int fastboot_bbu;
diff --git a/include/fastboot_net.h b/include/fastboot_net.h
new file mode 100644
index 0000000000..e4b9d98091
--- /dev/null
+++ b/include/fastboot_net.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef __FASTBOOT_NET__
+#define __FASTBOOT_NET__
+
+#include <fastboot.h>
+
+struct fastboot_net;
+
+struct fastboot_net *fastboot_net_init(struct fastboot_opts *opts);
+void fastboot_net_free(struct fastboot_net *fbn);
+
+#endif
diff --git a/net/Kconfig b/net/Kconfig
index 12b6bdb56d..4ed5c6de9d 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -31,4 +31,22 @@ config NET_SNTP
bool
prompt "sntp support"
+config NET_FASTBOOT
+ bool
+ select BANNER
+ select FILE_LIST
+ select FASTBOOT_BASE
+ select IDLE_SLICE
+ prompt "Android Fastboot support"
+ help
+ This option adds support for the UDP variant of the Fastboot
+ protocol.
+
+config FASTBOOT_NET_ON_BOOT
+ bool
+ depends on NET_FASTBOOT
+ prompt "Start network fastboot during boot"
+ help
+ Automatically starts the network and listens for Fastboot packets.
+ The list of accessible files is taken from global.fastboot.partitions.
endif
diff --git a/net/Makefile b/net/Makefile
index eb8d439150..962b2dec58 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_CMD_PING) += ping.o
obj-$(CONFIG_NET_RESOLV)+= dns.o
obj-$(CONFIG_NET_NETCONSOLE) += netconsole.o
obj-$(CONFIG_NET_IFUP) += ifup.o
+obj-$(CONFIG_NET_FASTBOOT) += fastboot.o
diff --git a/net/fastboot.c b/net/fastboot.c
new file mode 100644
index 0000000000..9ea1a8c5b4
--- /dev/null
+++ b/net/fastboot.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Copyright 2020 Edmund Henniges <eh@emlix.com>
+ * Copyright 2020 Daniel Glöckner <dg@emlix.com>
+ * Ported from U-Boot to Barebox
+ */
+
+#define pr_fmt(fmt) "net fastboot: " fmt
+
+#include <common.h>
+#include <net.h>
+#include <fastboot.h>
+#include <fastboot_net.h>
+#include <environment.h>
+#include <progress.h>
+#include <unistd.h>
+#include <init.h>
+
+#define FASTBOOT_PORT 5554
+#define MAX_MTU 1500
+#define PACKET_SIZE (min(PKTSIZE, MAX_MTU + ETHER_HDR_SIZE) \
+ - (net_eth_to_udp_payload(0) - (char *)0))
+
+enum {
+ FASTBOOT_ERROR = 0,
+ FASTBOOT_QUERY = 1,
+ FASTBOOT_INIT = 2,
+ FASTBOOT_FASTBOOT = 3,
+};
+
+enum {
+ MAY_NOT_SEND,
+ MAY_SEND_MESSAGE,
+ MAY_SEND_ACK,
+};
+
+struct __packed fastboot_header {
+ u8 id;
+ u8 flags;
+ u16 seq;
+};
+
+struct fastboot_net {
+ struct fastboot fastboot;
+
+ struct net_connection *net_con;
+ struct fastboot_header response_header;
+ struct poller_struct poller;
+ struct poller_struct keep_alive_poller;
+ u64 host_waits_since;
+ bool sequence_number_seen;
+ bool active_download;
+ bool download_needs_cleanup;
+ bool reinit;
+ u8 may_send;
+ char command[65];
+
+ IPaddr_t host_addr;
+ u16 host_port;
+ u8 host_mac[ETH_ALEN];
+ u16 sequence_number;
+ u16 last_payload_len;
+ uchar last_payload[64 + sizeof(struct fastboot_header)];
+};
+
+static const ushort udp_version = 1;
+
+static bool is_current_connection(struct fastboot_net *fbn)
+{
+ return fbn->host_addr == net_read_ip(&fbn->net_con->ip->daddr) &&
+ fbn->host_port == fbn->net_con->udp->uh_dport;
+}
+
+static void fastboot_send(struct fastboot_net *fbn,
+ struct fastboot_header header,
+ const char *error_msg)
+{
+ short tmp;
+ uchar *packet = net_udp_get_payload(fbn->net_con);
+ uchar *packet_base = packet;
+ bool current_session = false;
+
+ if (fbn->sequence_number == ntohs(header.seq) &&
+ is_current_connection(fbn))
+ current_session = true;
+
+ if (error_msg)
+ header.id = FASTBOOT_ERROR;
+
+ /* send header */
+ memcpy(packet, &header, sizeof(header));
+ packet += sizeof(header);
+
+ switch (header.id) {
+ case FASTBOOT_QUERY:
+ /* send sequence number */
+ tmp = htons(fbn->sequence_number);
+ memcpy(packet, &tmp, sizeof(tmp));
+ packet += sizeof(tmp);
+ break;
+ case FASTBOOT_INIT:
+ /* send udp version and packet size */
+ tmp = htons(udp_version);
+ memcpy(packet, &tmp, sizeof(tmp));
+ packet += sizeof(tmp);
+ tmp = htons(PACKET_SIZE);
+ memcpy(packet, &tmp, sizeof(tmp));
+ packet += sizeof(tmp);
+ break;
+ case FASTBOOT_ERROR:
+ pr_err("%s\n", error_msg);
+
+ /* send error message */
+ tmp = strlen(error_msg);
+ memcpy(packet, error_msg, tmp);
+ packet += tmp;
+
+ if (current_session) {
+ fbn->fastboot.active = false;
+ fbn->active_download = false;
+ fbn->reinit = true;
+ }
+ break;
+ }
+
+ if (current_session && header.id != FASTBOOT_QUERY) {
+ fbn->sequence_number++;
+ fbn->sequence_number_seen = false;
+ fbn->last_payload_len = packet - packet_base;
+ memcpy(fbn->last_payload, packet_base, fbn->last_payload_len);
+ }
+ net_udp_send(fbn->net_con, packet - packet_base);
+}
+
+static int fastboot_write_net(struct fastboot *fb, const char *buf,
+ unsigned int n)
+{
+ struct fastboot_net *fbn = container_of(fb, struct fastboot_net,
+ fastboot);
+ struct fastboot_header response_header;
+ uchar *packet;
+ uchar *packet_base;
+
+ if (fbn->reinit)
+ return 0;
+
+ while (fbn->may_send == MAY_NOT_SEND) {
+ poller_call();
+ if (fbn->reinit)
+ return 0;
+ }
+
+ if (n && fbn->may_send == MAY_SEND_ACK) {
+ fastboot_send(fbn, fbn->response_header,
+ "Have message but only ACK allowed");
+ return -EPROTO;
+ } else if (!n && fbn->may_send == MAY_SEND_MESSAGE) {
+ fastboot_send(fbn, fbn->response_header,
+ "Want to send ACK but message expected");
+ return -EPROTO;
+ }
+
+ response_header = fbn->response_header;
+ response_header.flags = 0;
+ response_header.seq = htons(fbn->sequence_number);
+ ++fbn->sequence_number;
+ fbn->sequence_number_seen = false;
+ fbn->may_send = MAY_NOT_SEND;
+
+ packet = net_udp_get_payload(fbn->net_con);
+ packet_base = packet;
+
+ /* Write headers */
+ memcpy(packet, &response_header, sizeof(response_header));
+ packet += sizeof(response_header);
+ /* Write response */
+ memcpy(packet, buf, n);
+ packet += n;
+
+ /* Save packet for retransmitting */
+ fbn->last_payload_len = packet - packet_base;
+ memcpy(fbn->last_payload, packet_base, fbn->last_payload_len);
+
+ memcpy(fbn->net_con->et->et_dest, fbn->host_mac, ETH_ALEN);
+ net_write_ip(&fbn->net_con->ip->daddr, fbn->host_addr);
+ fbn->net_con->udp->uh_dport = fbn->host_port;
+ net_udp_send(fbn->net_con, fbn->last_payload_len);
+
+ return 0;
+}
+
+static void fastboot_start_download_net(struct fastboot *fb)
+{
+ struct fastboot_net *fbn = container_of(fb, struct fastboot_net,
+ fastboot);
+
+ fastboot_start_download_generic(fb);
+ fbn->active_download = true;
+}
+
+/* must send exactly one packet on all code paths */
+static void fastboot_data_download(struct fastboot_net *fbn,
+ const void *fastboot_data,
+ unsigned int fastboot_data_len)
+{
+ int ret;
+
+ if (fastboot_data_len == 0 ||
+ (fbn->fastboot.download_bytes + fastboot_data_len) >
+ fbn->fastboot.download_size) {
+ fastboot_send(fbn, fbn->response_header,
+ "Received invalid data length");
+ return;
+ }
+
+ ret = fastboot_handle_download_data(&fbn->fastboot, fastboot_data,
+ fastboot_data_len);
+ if (ret < 0) {
+ fastboot_send(fbn, fbn->response_header, strerror(-ret));
+ return;
+ }
+
+ fastboot_tx_print(&fbn->fastboot, FASTBOOT_MSG_NONE, "");
+}
+
+static void fastboot_handle_type_fastboot(struct fastboot_net *fbn,
+ struct fastboot_header header,
+ char *fastboot_data,
+ unsigned int fastboot_data_len)
+{
+ fbn->response_header = header;
+ fbn->host_waits_since = get_time_ns();
+ fbn->may_send = fastboot_data_len ? MAY_SEND_ACK : MAY_SEND_MESSAGE;
+ if (fbn->active_download) {
+ if (!fastboot_data_len && fbn->fastboot.download_bytes
+ == fbn->fastboot.download_size) {
+ /*
+ * Can't call fastboot_download_finished here
+ * because it will call fastboot_tx_print
+ * multiple times.
+ */
+ fbn->active_download = false;
+ } else {
+ fastboot_data_download(fbn, fastboot_data,
+ fastboot_data_len);
+ }
+ } else if (!fbn->command[0]) {
+ if (fastboot_data_len >= sizeof(fbn->command)) {
+ fastboot_send(fbn, header, "command too long");
+ } else {
+ memcpy(fbn->command, fastboot_data, fastboot_data_len);
+ fbn->command[fastboot_data_len] = 0;
+ }
+ }
+
+ if (!fbn->fastboot.active)
+ fbn->active_download = false;
+}
+
+static void fastboot_check_retransmit(struct fastboot_net *fbn,
+ struct fastboot_header header)
+{
+ if (ntohs(header.seq) == fbn->sequence_number - 1 &&
+ is_current_connection(fbn)) {
+ /* Retransmit last sent packet */
+ memcpy(net_udp_get_payload(fbn->net_con),
+ fbn->last_payload, fbn->last_payload_len);
+ net_udp_send(fbn->net_con, fbn->last_payload_len);
+ }
+}
+
+static void fastboot_handler(void *ctx, char *packet, unsigned int raw_len)
+{
+ unsigned int len = net_eth_to_udplen(packet);
+ struct ethernet *eth_header = (struct ethernet *)packet;
+ struct iphdr *ip_header = net_eth_to_iphdr(packet);
+ struct udphdr *udp_header = net_eth_to_udphdr(packet);
+ char *payload = net_eth_to_udp_payload(packet);
+ struct fastboot_net *fbn = ctx;
+ struct fastboot_header header;
+ char *fastboot_data = payload + sizeof(header);
+ u16 tot_len = ntohs(ip_header->tot_len);
+
+ /* catch bogus tot_len values */
+ if ((char *)ip_header - packet + tot_len > raw_len)
+ return;
+
+ /* catch packets split into fragments that are too small to reply */
+ if (fastboot_data - (char *)ip_header > tot_len)
+ return;
+
+ /* catch packets too small to be valid */
+ if (len < sizeof(struct fastboot_header))
+ return;
+
+ memcpy(&header, payload, sizeof(header));
+ header.flags = 0;
+ len -= sizeof(header);
+
+ /* catch remaining fragmented packets */
+ if (fastboot_data - (char *)ip_header + len > tot_len) {
+ fastboot_send(fbn, header,
+ "can't reassemble fragmented frames");
+ return;
+ }
+ /* catch too large packets */
+ if (len > PACKET_SIZE) {
+ fastboot_send(fbn, header, "packet too large");
+ return;
+ }
+
+ memcpy(fbn->net_con->et->et_dest, eth_header->et_src, ETH_ALEN);
+ net_copy_ip(&fbn->net_con->ip->daddr, &ip_header->saddr);
+ fbn->net_con->udp->uh_dport = udp_header->uh_sport;
+
+ switch (header.id) {
+ case FASTBOOT_QUERY:
+ fastboot_send(fbn, header, NULL);
+ break;
+ case FASTBOOT_INIT:
+ if (ntohs(header.seq) != fbn->sequence_number) {
+ fastboot_check_retransmit(fbn, header);
+ break;
+ }
+ fbn->host_addr = net_read_ip(&ip_header->saddr);
+ fbn->host_port = udp_header->uh_sport;
+ memcpy(fbn->host_mac, eth_header->et_src, ETH_ALEN);
+ fbn->reinit = true;
+ if (fbn->active_download) {
+ /*
+ * it is safe to call
+ * fastboot_download_finished here
+ * because reinit is true
+ */
+ fastboot_download_finished(&fbn->fastboot);
+ fbn->active_download = false;
+ }
+ fastboot_send(fbn, header, NULL);
+ break;
+ case FASTBOOT_FASTBOOT:
+ if (!is_current_connection(fbn))
+ break;
+ memcpy(fbn->host_mac, eth_header->et_src, ETH_ALEN);
+ if (ntohs(header.seq) != fbn->sequence_number) {
+ fastboot_check_retransmit(fbn, header);
+ } else if (!fbn->sequence_number_seen) {
+ fbn->sequence_number_seen = true;
+ fastboot_handle_type_fastboot(fbn, header,
+ fastboot_data, len);
+ }
+ break;
+ default:
+ fastboot_send(fbn, header, "unknown packet type");
+ break;
+ }
+}
+
+static void fastboot_poll(struct poller_struct *poller)
+{
+ struct fastboot_net *fbn = container_of(poller, struct fastboot_net,
+ poller);
+
+ if (fbn->download_needs_cleanup) {
+ if (fbn->active_download)
+ return;
+ if (!fbn->reinit)
+ fastboot_download_finished(&fbn->fastboot);
+ fbn->download_needs_cleanup = 0;
+ }
+
+ if (!fbn->command[0])
+ return;
+
+ fbn->reinit = false;
+ fastboot_tx_print(&fbn->fastboot, FASTBOOT_MSG_NONE, "");
+ fastboot_exec_cmd(&fbn->fastboot, fbn->command);
+ fbn->command[0] = 0;
+ fbn->download_needs_cleanup = fbn->active_download;
+}
+
+static void fastboot_send_keep_alive(struct poller_struct *poller)
+{
+ struct fastboot_net *fbn = container_of(poller, struct fastboot_net,
+ keep_alive_poller);
+
+ /*
+ * Sending the message will reset may_send to MAY_NOT_SEND and the
+ * ACK from the host will reset host_waits_since to the current time.
+ */
+ if (fbn->may_send == MAY_SEND_MESSAGE &&
+ is_timeout_non_interruptible(fbn->host_waits_since,
+ 30ULL * NSEC_PER_SEC))
+ fastboot_tx_print(&fbn->fastboot, FASTBOOT_MSG_INFO,
+ "still busy");
+}
+
+void fastboot_net_free(struct fastboot_net *fbn)
+{
+ fastboot_generic_close(&fbn->fastboot);
+ poller_unregister(&fbn->keep_alive_poller);
+ poller_unregister(&fbn->poller);
+ net_unregister(fbn->net_con);
+ fastboot_generic_free(&fbn->fastboot);
+ free(fbn);
+}
+
+struct fastboot_net *fastboot_net_init(struct fastboot_opts *opts)
+{
+ struct fastboot_net *fbn;
+ int ret;
+
+ fbn = xzalloc(sizeof(*fbn));
+ INIT_LIST_HEAD(&fbn->fastboot.variables);
+ fbn->fastboot.write = fastboot_write_net;
+ fbn->fastboot.start_download = fastboot_start_download_net;
+
+ if (opts) {
+ fbn->fastboot.files = opts->files;
+ fbn->fastboot.cmd_exec = opts->cmd_exec;
+ fbn->fastboot.cmd_flash = opts->cmd_flash;
+ ret = fastboot_generic_init(&fbn->fastboot, opts->export_bbu);
+ } else {
+ fbn->fastboot.files = file_list_parse(fastboot_partitions
+ ? fastboot_partitions
+ : "");
+ ret = fastboot_generic_init(&fbn->fastboot, fastboot_bbu);
+ }
+ if (ret)
+ goto fail_generic_init;
+
+ fbn->net_con = net_udp_new(IP_BROADCAST, FASTBOOT_PORT,
+ fastboot_handler, fbn);
+ if (IS_ERR(fbn->net_con)) {
+ ret = PTR_ERR(fbn->net_con);
+ goto fail_net_con;
+ }
+ net_udp_bind(fbn->net_con, FASTBOOT_PORT);
+
+ eth_open(fbn->net_con->edev);
+ fbn->poller.func = fastboot_poll;
+ ret = poller_register(&fbn->poller, "fastboot-net");
+ if (ret)
+ goto fail_poller;
+
+ fbn->keep_alive_poller.func = fastboot_send_keep_alive;
+ ret = poller_register(&fbn->keep_alive_poller,
+ "fastboot-net-keep-alive");
+ if (ret)
+ goto fail_poller2;
+
+ slice_depends_on(&fbn->poller.slice, &idle_slice);
+ slice_depends_on(&fbn->keep_alive_poller.slice, &fbn->net_con->edev->slice);
+
+ return fbn;
+
+fail_poller2:
+ poller_unregister(&fbn->poller);
+fail_poller:
+ net_unregister(fbn->net_con);
+fail_net_con:
+ fastboot_generic_free(&fbn->fastboot);
+fail_generic_init:
+ free(fbn);
+ return ERR_PTR(ret);
+}
+
+#ifdef CONFIG_FASTBOOT_NET_ON_BOOT
+static struct fastboot_net *fastboot_net_obj;
+
+static int fastboot_on_boot(void)
+{
+ struct fastboot_net *fbn;
+
+ ifup_all(0);
+ fbn = fastboot_net_init(NULL);
+
+ if (IS_ERR(fbn))
+ return PTR_ERR(fbn);
+
+ fastboot_net_obj = fbn;
+ return 0;
+}
+
+static void fastboot_net_exit(void)
+{
+ if (fastboot_net_obj)
+ fastboot_net_free(fastboot_net_obj);
+}
+
+postenvironment_initcall(fastboot_on_boot);
+predevshutdown_exitcall(fastboot_net_exit);
+#endif
--
2.26.2
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
next prev parent reply other threads:[~2020-05-20 9:44 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-05-20 9:43 [PATCH 00/19] Slices and " Sascha Hauer
2020-05-20 9:43 ` [PATCH 01/19] poller: Give pollers a name Sascha Hauer
2020-05-20 9:43 ` [PATCH 02/19] poller: Add a poller command Sascha Hauer
2020-05-20 9:43 ` [PATCH 03/19] fastboot: split generic code from USB gadget Sascha Hauer
2020-05-20 9:43 ` [PATCH 04/19] fastboot: don't close fd 0 when downloading to ram Sascha Hauer
2020-05-20 9:43 ` [PATCH 05/19] fastboot: Use unique tempfile name Sascha Hauer
2020-05-20 9:43 ` [PATCH 06/19] Introduce slices Sascha Hauer
2020-05-20 9:43 ` [PATCH 07/19] Introduce idle slice Sascha Hauer
2020-05-22 11:56 ` Daniel Glöckner
2020-05-25 8:09 ` Sascha Hauer
2020-05-20 9:43 ` [PATCH 08/19] net: Add a slice to struct eth_device Sascha Hauer
2020-05-20 9:43 ` [PATCH 09/19] net: mdiobus: Add slice Sascha Hauer
2020-05-20 9:43 ` [PATCH 10/19] usb: Add a slice to usb host controllers Sascha Hauer
2020-05-20 9:43 ` [PATCH 11/19] usbnet: Add slice Sascha Hauer
2020-05-20 9:43 ` [PATCH 12/19] net: Call net_poll() in a poller Sascha Hauer
2020-05-20 9:43 ` [PATCH 13/19] net: reply to ping requests Sascha Hauer
2020-05-20 9:43 ` [PATCH 14/19] usbnet: Be more friendly in the receive path Sascha Hauer
2020-05-20 9:43 ` [PATCH 15/19] poller: Allow to run pollers inside of pollers Sascha Hauer
2020-05-20 9:44 ` [PATCH 16/19] defconfigs: update renamed fastboot options Sascha Hauer
2020-05-20 9:44 ` [PATCH 17/19] fastboot: rename usbgadget.fastboot_* variables to fastboot.* Sascha Hauer
2020-05-20 9:44 ` Sascha Hauer [this message]
2020-05-20 9:44 ` [PATCH 19/19] fastboot net: remove may_send Sascha Hauer
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=20200520094403.12651-19-s.hauer@pengutronix.de \
--to=s.hauer@pengutronix.de \
--cc=barebox@lists.infradead.org \
--cc=dg@emlix.com \
--cc=eh@emlix.com \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox