From: Edmund Henniges <[email protected]>

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 <[email protected]>
Signed-off-by: Daniel Glöckner <[email protected]>
Signed-off-by: Sascha Hauer <[email protected]>
---
 common/fastboot.c      |   3 +
 include/fastboot.h     |   3 +
 include/fastboot_net.h |  12 +
 net/Kconfig            |  10 +
 net/Makefile           |   1 +
 net/fastboot.c         | 565 +++++++++++++++++++++++++++++++++++++++++
 6 files changed, 594 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 3f61208a21..d737107d19 100644
--- a/common/fastboot.c
+++ b/common/fastboot.c
@@ -245,6 +245,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,
@@ -273,6 +274,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;
        }
@@ -287,6 +289,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 7718390ae5..fa826a1622 100644
--- a/include/fastboot.h
+++ b/include/fastboot.h
@@ -5,6 +5,8 @@
 #include <file-list.h>
 #include <net.h>
 
+#define FASTBOOT_MAX_CMD_LEN  64
+
 /*
  * Return codes for the exec_cmd callback above:
  *
@@ -51,6 +53,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..a3c8c10f33 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -31,4 +31,14 @@ config NET_SNTP
        bool
        prompt "sntp support"
 
+config NET_FASTBOOT
+       bool
+       select BANNER
+       select FILE_LIST
+       select FASTBOOT_BASE
+       prompt "Android Fastboot support"
+       help
+         This option adds support for the UDP variant of the Fastboot
+         protocol.
+
 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..e839ee1838
--- /dev/null
+++ b/net/fastboot.c
@@ -0,0 +1,565 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Copyright 2020 Edmund Henniges <[email protected]>
+ * Copyright 2020 Daniel Glöckner <[email protected]>
+ * 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>
+#include <work.h>
+#include <globalvar.h>
+#include <magicvar.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_send {
+       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 work_queue wq;
+       u64 host_waits_since;
+       u64 last_download_pkt;
+       bool sequence_number_seen;
+       bool active_download;
+       bool reinit;
+       bool send_keep_alive;
+       enum may_send may_send;
+
+       IPaddr_t host_addr;
+       u16 host_port;
+       u8 host_mac[ETH_ALEN];
+       u16 sequence_number;
+       u16 last_payload_len;
+       uchar last_payload[FASTBOOT_MAX_CMD_LEN + 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_net_abort(struct fastboot_net *fbn)
+{
+       fbn->reinit = true;
+
+       fastboot_abort(&fbn->fastboot);
+
+       fbn->active_download = false;
+
+       poller_unregister(&fbn->poller);
+
+       /*
+        * If the host sends a data packet at a time when an empty packet was
+        * expected, fastboot_abort is called and an error message is sent.
+        * We don't want to execute the contents of the bad packet afterwards.
+        * Clearing command also tells our keep-alive poller to stop sending
+        * messages.
+        */
+       wq_cancel_work(&fbn->wq);
+}
+
+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)
+                       fastboot_net_abort(fbn);
+
+               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_net_wait_may_send(struct fastboot_net *fbn)
+{
+       uint64_t start = get_time_ns();
+
+       while (!is_timeout(start, 2 * SECOND)) {
+               if (fbn->may_send != MAY_NOT_SEND)
+                       return 0;
+       }
+
+       return -ETIMEDOUT;
+}
+
+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;
+       int ret;
+
+       if (fbn->reinit)
+               return 0;
+
+       /*
+        * This function is either called in command context, in which
+        * case we may wait, or from the keepalive poller which explicitly
+        * only calls us when we don't have to wait here.
+        */
+       ret = fastboot_net_wait_may_send(fbn);
+       if (ret) {
+               fastboot_net_abort(fbn);
+               return ret;
+       }
+
+       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;
+
+       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);
+
+       fbn->may_send = MAY_NOT_SEND;
+
+       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;
+       fbn->last_download_pkt = get_time_ns();
+}
+
+/* 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, "");
+}
+
+struct fastboot_work {
+       struct work_struct work;
+       struct fastboot_net *fbn;
+       bool download_finished;
+       char command[FASTBOOT_MAX_CMD_LEN + 1];
+};
+
+static void fastboot_handle_type_fastboot(struct fastboot_net *fbn,
+                                         struct fastboot_header header,
+                                         char *fastboot_data,
+                                         unsigned int fastboot_data_len)
+{
+       struct fastboot_work *w;
+
+       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) {
+               fbn->last_download_pkt = get_time_ns();
+
+               if (!fastboot_data_len && fbn->fastboot.download_bytes
+                                          == fbn->fastboot.download_size) {
+
+                       fbn->active_download = false;
+
+                       w = xzalloc(sizeof(*w));
+                       w->fbn = fbn;
+                       w->download_finished = true;
+
+                       wq_queue_work(&fbn->wq, &w->work);
+               } else {
+                       fastboot_data_download(fbn, fastboot_data,
+                                              fastboot_data_len);
+               }
+               return;
+       }
+
+       if (fastboot_data_len >= FASTBOOT_MAX_CMD_LEN) {
+               fastboot_send(fbn, header, "command too long");
+               return;
+       }
+
+       if (!list_empty(&fbn->wq.work))
+               return;
+
+       if (fastboot_data_len) {
+               w = xzalloc(sizeof(*w));
+               w->fbn = fbn;
+               memcpy(w->command, fastboot_data, fastboot_data_len);
+               w->command[fastboot_data_len] = 0;
+
+               wq_queue_work(&fbn->wq, &w->work);
+       }
+}
+
+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);
+       int ret;
+
+       /* 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);
+               fastboot_net_abort(fbn);
+               ret = poller_register(&fbn->poller, "fastboot");
+               if (ret) {
+                       pr_err("Cannot register poller: %s\n", strerror(-ret));
+                       return;
+               }
+               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_do_work(struct work_struct *w)
+{
+       struct fastboot_work *fw = container_of(w, struct fastboot_work, work);
+       struct fastboot_net *fbn = fw->fbn;
+
+       if (fw->download_finished) {
+               fastboot_download_finished(&fbn->fastboot);
+               goto out;
+       }
+
+       fbn->reinit = false;
+       fastboot_tx_print(&fbn->fastboot, FASTBOOT_MSG_NONE, "");
+
+       fbn->send_keep_alive = true;
+
+       fastboot_exec_cmd(&fbn->fastboot, fw->command);
+       fbn->send_keep_alive = false;
+out:
+       free(fw);
+}
+
+static void fastboot_work_cancel(struct work_struct *w)
+{
+       struct fastboot_work *fw = container_of(w, struct fastboot_work, work);
+
+       free(fw);
+}
+
+static void fastboot_poll(struct poller_struct *poller)
+{
+       struct fastboot_net *fbn = container_of(poller, struct fastboot_net,
+                                              poller);
+
+       if (fbn->active_download && is_timeout(fbn->last_download_pkt, 5 * 
SECOND)) {
+               pr_err("No progress for 5s, aborting\n");
+               fastboot_net_abort(fbn);
+               return;
+       }
+
+       if (!fbn->send_keep_alive)
+               return;
+
+       if (!is_timeout_non_interruptible(fbn->host_waits_since,
+                                        30ULL * NSEC_PER_SEC))
+               return;
+
+       if (fbn->may_send != MAY_SEND_MESSAGE)
+               return;
+
+       fastboot_tx_print(&fbn->fastboot, FASTBOOT_MSG_INFO, "still busy");
+}
+
+void fastboot_net_free(struct fastboot_net *fbn)
+{
+       fastboot_generic_close(&fbn->fastboot);
+       net_unregister(fbn->net_con);
+       fastboot_generic_free(&fbn->fastboot);
+       wq_unregister(&fbn->wq);
+       free(fbn);
+}
+
+struct fastboot_net *fastboot_net_init(struct fastboot_opts *opts)
+{
+       struct fastboot_net *fbn;
+       int ret;
+
+       fbn = xzalloc(sizeof(*fbn));
+       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;
+
+       fbn->wq.fn = fastboot_do_work;
+       fbn->wq.cancel = fastboot_work_cancel;
+
+       wq_register(&fbn->wq);
+
+       return fbn;
+
+fail_net_con:
+       fastboot_generic_free(&fbn->fastboot);
+fail_generic_init:
+       free(fbn);
+       return ERR_PTR(ret);
+}
+
+static struct fastboot_net *fastboot_net_obj;
+static int fastboot_net_autostart;
+
+static int fastboot_on_boot(void)
+{
+       struct fastboot_net *fbn;
+
+       globalvar_add_simple_bool("fastboot.net.autostart",
+                                 &fastboot_net_autostart);
+
+       if (!fastboot_net_autostart)
+               return 0;
+
+       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);
+
+BAREBOX_MAGICVAR_NAMED(global_fastboot_net_autostart,
+                      global.fastboot.net.autostart,
+                      "If true, automatically start fastboot over UDP during 
startup");
-- 
2.27.0


_______________________________________________
barebox mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/barebox

Reply via email to