mail archive of the barebox mailing list
 help / color / mirror / Atom feed
* [PATCH 0/3] Support for fastboot over UDP
@ 2020-02-28 20:48 Daniel Glöckner
  2020-02-28 20:48 ` [PATCH 1/3] fastboot: split generic code from USB gadget Daniel Glöckner
                   ` (4 more replies)
  0 siblings, 5 replies; 12+ messages in thread
From: Daniel Glöckner @ 2020-02-28 20:48 UTC (permalink / raw)
  To: barebox; +Cc: Daniel Glöckner

This patch series adds support the UDP variant of the fastboot protocol
that was already supported in its USB form by Barebox. There is also a
TCP variant, but Barebox obviously can't support that one with its
current network stack.

The first patch separates all functions that can be reused into a file in
common/. Although git didn't pick up the similarities, common/fastboot.c
can be easily diffed against the old drivers/usb/gadget/f_fastboot.c,
especially after replacing struct f_fb with fb and struct f_fastboot with
struct fastboot.

The global.usbgadget.fastboot_max_download_size variable has not been
renamed because someone might expect it to have that name. But I'll
happily drop the usbgadget part from the name if you prefer.

The generic Kconfig options have been renamed. We could add options
with the old name that select the new options to make the transition
easier.

The second patch contains the implementation. The code was initially
taken from U-Boot, but had to be rewritten heavily to fit the way the
existing Barebox fastboot code executes commands and prints messages.
Contrary to U-Boot it does not send keep alive messages every 30
seconds because everything is executed from a poller. The timeout of
Google's fastboot tool is 60 seconds.

The third patch was necessary to allow network interfaces to be activated
without sending packets. The fastboot code sends packets only in reply to
received packets.

Daniel Glöckner (1):
  fastboot net: workaround for receiving before sending

Edmund Henniges (2):
  fastboot: split generic code from USB gadget
  fastboot net: implement fastboot over UDP

 common/Kconfig                  |  35 ++
 common/Makefile                 |   1 +
 common/fastboot.c               | 958 +++++++++++++++++++++++++++++++
 drivers/usb/gadget/Kconfig      |   3 +-
 drivers/usb/gadget/f_fastboot.c | 968 ++------------------------------
 drivers/usb/gadget/multi.c      |   5 +-
 include/fastboot.h              |  67 +++
 include/fastboot_net.h          |  12 +
 include/net.h                   |   1 +
 include/usb/fastboot.h          |  34 +-
 include/usb/gadget-multi.h      |   2 +-
 net/Kconfig                     |  17 +
 net/Makefile                    |   1 +
 net/eth.c                       |   2 +-
 net/fastboot.c                  | 438 +++++++++++++++
 15 files changed, 1579 insertions(+), 965 deletions(-)
 create mode 100644 common/fastboot.c
 create mode 100644 include/fastboot.h
 create mode 100644 include/fastboot_net.h
 create mode 100644 net/fastboot.c

-- 
2.17.1


_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH 1/3] fastboot: split generic code from USB gadget
  2020-02-28 20:48 [PATCH 0/3] Support for fastboot over UDP Daniel Glöckner
@ 2020-02-28 20:48 ` Daniel Glöckner
  2020-03-05  7:25   ` Sascha Hauer
  2020-02-28 20:48 ` [PATCH 2/3] fastboot net: implement fastboot over UDP Daniel Glöckner
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 12+ messages in thread
From: Daniel Glöckner @ 2020-02-28 20:48 UTC (permalink / raw)
  To: barebox; +Cc: Edmund Henniges, Daniel Glöckner

From: Edmund Henniges <eh@emlix.com>

The fastboot specification describes other protocols beyond USB. Allow
these to reuse the generic parts of the existing fastboot code when they
are implemented.

Most of the changes in common/fastboot.c are due to the renaming of struct
f_fastboot *f_fb to struct fastboot *fb.

Signed-off-by: Edmund Henniges <eh@emlix.com>
Signed-off-by: Daniel Glöckner <dg@emlix.com>
---
 common/Kconfig                  |  35 ++
 common/Makefile                 |   1 +
 common/fastboot.c               | 955 +++++++++++++++++++++++++++++++
 drivers/usb/gadget/Kconfig      |   3 +-
 drivers/usb/gadget/f_fastboot.c | 968 ++------------------------------
 drivers/usb/gadget/multi.c      |   5 +-
 include/fastboot.h              |  66 +++
 include/usb/fastboot.h          |  34 +-
 include/usb/gadget-multi.h      |   2 +-
 9 files changed, 1105 insertions(+), 964 deletions(-)
 create mode 100644 common/fastboot.c
 create mode 100644 include/fastboot.h

diff --git a/common/Kconfig b/common/Kconfig
index 02ef3631e..bed7e49db 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -114,6 +114,9 @@ config USBGADGET_START
 config BOOT
 	bool
 
+config FASTBOOT_BASE
+	bool
+
 menu "General Settings"
 
 config LOCALVERSION
@@ -1013,6 +1016,38 @@ config PBL_OPTEE
 
 endmenu
 
+config FASTBOOT_SPARSE
+	bool
+	depends on FASTBOOT_BASE
+	select IMAGE_SPARSE
+	prompt "Enable Fastboot sparse image support"
+	help
+	  Sparse images are a way for the fastboot protocol to write
+	  images that are bigger than the available memory. If unsure,
+	  say yes here.
+
+config FASTBOOT_BUF
+	bool
+	depends on FASTBOOT_BASE
+	prompt "Download files to temporary buffer instead of file"
+	help
+	  With this option enabled the fastboot code will download files to a
+	  temporary buffer instead of a temporary file. Normally you want to
+	  use a file as this also works when your memory is fragmented. However,
+	  in some special cases, when the file consumer also better copes with
+	  a buffer, then using a buffer might be better.
+
+	  Say no here unless you know what you are doing.
+
+config FASTBOOT_CMD_OEM
+	bool
+	depends on FASTBOOT_BASE
+	prompt "Enable OEM commands"
+	help
+	  This option enables the fastboot "oem" group of commands. They allow to
+	  executing arbitrary barebox commands and may be disabled in secure
+	  environments.
+
 endmenu
 
 menu "Debugging"
diff --git a/common/Makefile b/common/Makefile
index 84463b4d4..4222bce1c 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_SERIAL_DEV_BUS)	+= serdev.o
 obj-$(CONFIG_USBGADGET_START)	+= usbgadget.o
 pbl-$(CONFIG_PBL_OPTEE)		+= optee.o
 obj-$(CONFIG_BOOTM_OPTEE)	+= optee.o
+obj-$(CONFIG_FASTBOOT_BASE)	+= fastboot.o
 
 ifdef CONFIG_PASSWORD
 
diff --git a/common/fastboot.c b/common/fastboot.c
new file mode 100644
index 000000000..58a095efa
--- /dev/null
+++ b/common/fastboot.c
@@ -0,0 +1,955 @@
+/*
+ * (C) Copyright 2008 - 2009
+ * Windriver, <www.windriver.com>
+ * Tom Rix <Tom.Rix@windriver.com>
+ *
+ * Copyright 2011 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+ *
+ * Copyright 2014 Linaro, Ltd.
+ * Rob Herring <robh@kernel.org>
+ *
+ * Copyright 2014 Sascha Hauer <s.hauer@pengutronix.de>
+ * Ported to barebox
+ *
+ * Copyright 2020 Edmund Henniges <eh@emlix.com>
+ * Copyright 2020 Daniel Glöckner <dg@emlix.com>
+ * Split off of generic parts
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#define pr_fmt(fmt) "fastboot: " fmt
+
+#include <common.h>
+#include <command.h>
+#include <ioctl.h>
+#include <bbu.h>
+#include <bootm.h>
+#include <fs.h>
+#include <init.h>
+#include <libfile.h>
+#include <ubiformat.h>
+#include <unistd.h>
+#include <magicvar.h>
+#include <linux/sizes.h>
+#include <progress.h>
+#include <environment.h>
+#include <globalvar.h>
+#include <restart.h>
+#include <console_countdown.h>
+#include <image-sparse.h>
+#include <linux/types.h>
+#include <linux/stat.h>
+#include <linux/mtd/mtd.h>
+#include <fastboot.h>
+
+#define FASTBOOT_VERSION		"0.4"
+
+#define FASTBOOT_TMPFILE		"/.fastboot.img"
+
+static unsigned int fastboot_max_download_size = SZ_8M;
+
+struct fb_variable {
+	char *name;
+	char *value;
+	struct list_head list;
+};
+
+static inline bool fastboot_download_to_buf(struct fastboot *fb)
+{
+	if (IS_ENABLED(CONFIG_FASTBOOT_BUF))
+		return true;
+	else
+		return false;
+}
+
+static void fb_setvar(struct fb_variable *var, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	var->value = bvasprintf(fmt, ap);
+	va_end(ap);
+}
+
+static struct fb_variable *fb_addvar(struct fastboot *fb, const char *fmt, ...)
+{
+	struct fb_variable *var = xzalloc(sizeof(*var));
+	va_list ap;
+
+	va_start(ap, fmt);
+	var->name = bvasprintf(fmt, ap);
+	va_end(ap);
+
+	list_add_tail(&var->list, &fb->variables);
+
+	return var;
+}
+
+static int fastboot_add_partition_variables(struct fastboot *fb,
+		struct file_list_entry *fentry)
+{
+	struct stat s;
+	size_t size = 0;
+	int fd, ret;
+	struct mtd_info_user mtdinfo;
+	char *type = NULL;
+	struct fb_variable *var;
+
+	ret = stat(fentry->filename, &s);
+	if (ret) {
+		device_detect_by_name(devpath_to_name(fentry->filename));
+		ret = stat(fentry->filename, &s);
+	}
+
+	if (ret) {
+		if (fentry->flags & FILE_LIST_FLAG_CREATE) {
+			ret = 0;
+			type = "file";
+			goto out;
+		}
+
+		goto out;
+	}
+
+	fd = open(fentry->filename, O_RDWR);
+	if (fd < 0) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	size = s.st_size;
+
+	ret = ioctl(fd, MEMGETINFO, &mtdinfo);
+
+	close(fd);
+
+	if (!ret) {
+		switch (mtdinfo.type) {
+		case MTD_NANDFLASH:
+			type = "NAND-flash";
+			break;
+		case MTD_NORFLASH:
+			type = "NOR-flash";
+			break;
+		case MTD_UBIVOLUME:
+			type = "UBI";
+			break;
+		default:
+			type = "flash";
+			break;
+		}
+
+		goto out;
+	}
+
+	type = "basic";
+	ret = 0;
+
+out:
+	if (ret)
+		return ret;
+
+	var = fb_addvar(fb, "partition-size:%s", fentry->name);
+	fb_setvar(var, "%08zx", size);
+	var = fb_addvar(fb, "partition-type:%s", fentry->name);
+	fb_setvar(var, "%s", type);
+
+	return ret;
+}
+
+static int fastboot_add_bbu_variables(struct bbu_handler *handler, void *ctx)
+{
+	struct fastboot *fb = ctx;
+	char *name;
+	int ret;
+
+	name = basprintf("bbu-%s", handler->name);
+
+	ret = file_list_add_entry(fb->files, name, handler->devicefile, 0);
+
+	free(name);
+
+	return ret;
+}
+
+int fastboot_generic_init(struct fastboot *fb, bool export_bbu)
+{
+	int ret;
+	struct file_list_entry *fentry;
+	struct fb_variable *var;
+
+	var = fb_addvar(fb, "version");
+	fb_setvar(var, "0.4");
+	var = fb_addvar(fb, "bootloader-version");
+	fb_setvar(var, release_string);
+	if (IS_ENABLED(CONFIG_FASTBOOT_SPARSE)) {
+		var = fb_addvar(fb, "max-download-size");
+		fb_setvar(var, "%u", fastboot_max_download_size);
+	}
+
+	if (IS_ENABLED(CONFIG_BAREBOX_UPDATE) && export_bbu)
+		bbu_handlers_iterate(fastboot_add_bbu_variables, fb);
+
+	file_list_for_each_entry(fb->files, fentry) {
+		ret = fastboot_add_partition_variables(fb, fentry);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+void fastboot_generic_free(struct fastboot *fb)
+{
+	struct fb_variable *var, *tmp;
+
+	list_for_each_entry_safe(var, tmp, &fb->variables, list) {
+		free(var->name);
+		free(var->value);
+		list_del(&var->list);
+		free(var);
+	}
+
+	fb->active = false;
+}
+
+static struct fastboot *g_fb;
+
+void fastboot_generic_close(struct fastboot *fb)
+{
+	if (g_fb == fb)
+		g_fb = NULL;
+}
+
+/*
+ * A "oem exec bootm" or similar commands will stop barebox. Tell the
+ * fastboot command on the other side so that it doesn't run into a
+ * timeout.
+ */
+static void fastboot_shutdown(void)
+{
+	struct fastboot *fb = g_fb;
+
+	if (!fb || !fb->active)
+		return;
+
+	fastboot_tx_print(fb, FASTBOOT_MSG_INFO, "barebox shutting down");
+	fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, "");
+}
+
+early_exitcall(fastboot_shutdown);
+
+static char *fastboot_msg[] = {
+	[FASTBOOT_MSG_OKAY] = "OKAY",
+	[FASTBOOT_MSG_FAIL] = "FAIL",
+	[FASTBOOT_MSG_INFO] = "INFO",
+	[FASTBOOT_MSG_DATA] = "DATA",
+};
+
+int fastboot_tx_print(struct fastboot *fb, enum fastboot_msg_type type,
+		      const char *fmt, ...)
+{
+	struct va_format vaf;
+	char buf[64];
+	va_list ap;
+	int n;
+	const char *msg = fastboot_msg[type];
+
+	va_start(ap, fmt);
+	vaf.fmt = fmt;
+	vaf.va = &ap;
+
+	n = snprintf(buf, 64, "%s%pV", msg, &vaf);
+
+	switch (type) {
+	case FASTBOOT_MSG_OKAY:
+		fb->active = false;
+		break;
+	case FASTBOOT_MSG_FAIL:
+		fb->active = false;
+		pr_err("%pV\n", &vaf);
+		break;
+	case FASTBOOT_MSG_INFO:
+		pr_info("%pV\n", &vaf);
+		break;
+	case FASTBOOT_MSG_DATA:
+		break;
+	}
+
+	va_end(ap);
+
+	if (n > 64)
+		n = 64;
+
+	return fb->write(fb, buf, n);
+}
+
+static void cb_reboot(struct fastboot *fb, const char *cmd)
+{
+	restart_machine();
+}
+
+static int strcmp_l1(const char *s1, const char *s2)
+{
+	if (!s1 || !s2)
+		return -1;
+	return strncmp(s1, s2, strlen(s1));
+}
+
+static void cb_getvar(struct fastboot *fb, const char *cmd)
+{
+	struct fb_variable *var;
+
+	pr_debug("getvar: \"%s\"\n", cmd);
+
+	if (!strcmp_l1(cmd, "all")) {
+		list_for_each_entry(var, &fb->variables, list) {
+			fastboot_tx_print(fb, FASTBOOT_MSG_INFO, "%s: %s",
+					  var->name, var->value);
+		}
+		fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, "");
+		return;
+	}
+
+	list_for_each_entry(var, &fb->variables, list) {
+		if (!strcmp(cmd, var->name)) {
+			fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, var->value);
+			return;
+		}
+	}
+
+	fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, "");
+}
+
+int fastboot_handle_download_data(struct fastboot *fb, const void *buffer,
+				  unsigned int len)
+{
+	int ret;
+
+	if (fastboot_download_to_buf(fb)) {
+		memcpy(fb->buf + fb->download_bytes, buffer, len);
+	} else {
+		ret = write(fb->download_fd, buffer, len);
+		if (ret < 0)
+			return ret;
+	}
+
+	fb->download_bytes += len;
+	show_progress(fb->download_bytes);
+	return 0;
+}
+
+void fastboot_download_finished(struct fastboot *fb)
+{
+		close(fb->download_fd);
+
+		printf("\n");
+
+		fastboot_tx_print(fb, FASTBOOT_MSG_INFO, "Downloading %d bytes finished",
+				  fb->download_bytes);
+
+		fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, "");
+}
+
+static void cb_download(struct fastboot *fb, const char *cmd)
+{
+	fb->download_size = simple_strtoul(cmd, NULL, 16);
+	fb->download_bytes = 0;
+
+	fastboot_tx_print(fb, FASTBOOT_MSG_INFO, "Downloading %d bytes...",
+			  fb->download_size);
+
+	init_progression_bar(fb->download_size);
+
+	if (fastboot_download_to_buf(fb)) {
+		free(fb->buf);
+		fb->buf = malloc(fb->download_size);
+		if (!fb->buf) {
+			fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+					  "not enough memory");
+			return;
+		}
+	} else {
+		fb->download_fd = open(FASTBOOT_TMPFILE, O_WRONLY | O_CREAT | O_TRUNC);
+		if (fb->download_fd < 0) {
+			fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+					  "internal error");
+			return;
+		}
+	}
+
+	if (!fb->download_size)
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+					  "data invalid size");
+	else
+		fb->start_download(fb);
+}
+
+void fastboot_start_download_generic(struct fastboot *fb)
+{
+	fastboot_tx_print(fb, FASTBOOT_MSG_DATA, "%08x", fb->download_size);
+}
+
+static void __maybe_unused cb_boot(struct fastboot *fb, const char *opt)
+{
+	int ret;
+	struct bootm_data data = {
+		.initrd_address = UIMAGE_INVALID_ADDRESS,
+		.os_address = UIMAGE_SOME_ADDRESS,
+	};
+
+	fastboot_tx_print(fb, FASTBOOT_MSG_INFO, "Booting kernel..\n");
+
+	globalvar_set_match("linux.bootargs.dyn.", "");
+	globalvar_set_match("bootm.image", "");
+
+	data.os_file = FASTBOOT_TMPFILE;
+
+	ret = bootm_boot(&data);
+
+	if (ret)
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL, "Booting failed: %s",
+				   strerror(-ret));
+	else
+		fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, "");
+}
+
+static struct mtd_info *get_mtd(struct fastboot *fb, const char *filename)
+{
+	int fd, ret;
+	struct mtd_info_user meminfo;
+
+	fd = open(filename, O_RDONLY);
+	if (fd < 0)
+		return ERR_PTR(-errno);
+
+	ret = ioctl(fd, MEMGETINFO, &meminfo);
+
+	close(fd);
+
+	if (ret)
+		return ERR_PTR(ret);
+
+	return meminfo.mtd;
+}
+
+static int do_ubiformat(struct fastboot *fb, struct mtd_info *mtd,
+			const char *file, const void *buf, size_t len)
+{
+	struct ubiformat_args args = {
+		.yes = 1,
+		.image = file,
+		.image_buf = buf,
+		.image_size = len,
+	};
+
+	if (!file)
+		args.novtbl = 1;
+
+	if (!IS_ENABLED(CONFIG_UBIFORMAT)) {
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+					  "ubiformat is not available");
+		return -ENODEV;
+	}
+
+	return ubiformat(mtd, &args);
+}
+
+
+static int check_ubi(struct fastboot *fb, struct file_list_entry *fentry,
+		     enum filetype filetype)
+{
+	struct mtd_info *mtd;
+
+	mtd = get_mtd(fb, fentry->filename);
+
+	/*
+	 * Issue a warning when we are about to write a UBI image to a MTD device
+	 * and the FILE_LIST_FLAG_UBI is not given as this means we loose all
+	 * erase counters.
+	 */
+	if (!IS_ERR(mtd) && filetype == filetype_ubi &&
+	    !(fentry->flags & FILE_LIST_FLAG_UBI)) {
+		fastboot_tx_print(fb, FASTBOOT_MSG_INFO,
+				  "writing UBI image to MTD device, "
+				  "add the 'u' ");
+		fastboot_tx_print(fb, FASTBOOT_MSG_INFO,
+				  "flag to the partition description");
+		return 0;
+	}
+
+	if (!(fentry->flags & FILE_LIST_FLAG_UBI))
+		return 0;
+
+	if (!IS_ENABLED(CONFIG_UBIFORMAT)) {
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+				  "ubiformat not available");
+		return -ENOSYS;
+	}
+
+	if (IS_ERR(mtd)) {
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+				  "UBI flag given on non-MTD device");
+		return -EINVAL;
+	}
+
+	if (filetype == filetype_ubi) {
+		fastboot_tx_print(fb, FASTBOOT_MSG_INFO,
+				  "This is a UBI image...");
+		return 1;
+	} else {
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+				  "This is no UBI image but %s",
+				 file_type_to_string(filetype));
+		return -EINVAL;
+	}
+}
+
+static int fastboot_handle_sparse(struct fastboot *fb,
+				  struct file_list_entry *fentry)
+{
+	struct sparse_image_ctx *sparse;
+	void *buf = NULL;
+	int ret, fd;
+	unsigned int flags = O_RDWR;
+	int bufsiz = SZ_128K;
+	struct stat s;
+	struct mtd_info *mtd = NULL;
+
+	ret = stat(fentry->filename, &s);
+	if (ret) {
+		if (fentry->flags & FILE_LIST_FLAG_CREATE)
+			flags |= O_CREAT;
+		else
+			return ret;
+	}
+
+	fd = open(fentry->filename, flags);
+	if (fd < 0)
+		return -errno;
+
+	ret = fstat(fd, &s);
+	if (ret)
+		goto out_close_fd;
+
+	sparse = sparse_image_open(FASTBOOT_TMPFILE);
+	if (IS_ERR(sparse)) {
+		pr_err("Cannot open sparse image\n");
+		ret = PTR_ERR(sparse);
+		goto out_close_fd;
+	}
+
+	if (S_ISREG(s.st_mode)) {
+		ret = ftruncate(fd, sparse_image_size(sparse));
+		if (ret)
+			goto out;
+	}
+
+	buf = malloc(bufsiz);
+	if (!buf) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	if (fentry->flags & FILE_LIST_FLAG_UBI) {
+		mtd = get_mtd(fb, fentry->filename);
+		if (IS_ERR(mtd)) {
+			ret = PTR_ERR(mtd);
+			goto out;
+		}
+	}
+
+	while (1) {
+		int retlen;
+		loff_t pos;
+
+		ret = sparse_image_read(sparse, buf, &pos, bufsiz, &retlen);
+		if (ret)
+			goto out;
+		if (!retlen)
+			break;
+
+		if (pos == 0) {
+			ret = check_ubi(fb, fentry, file_detect_type(buf, retlen));
+			if (ret < 0)
+				goto out;
+		}
+
+		if (fentry->flags & FILE_LIST_FLAG_UBI) {
+			if (!IS_ENABLED(CONFIG_UBIFORMAT)) {
+				ret = -ENOSYS;
+				goto out;
+			}
+
+			if (pos == 0) {
+				ret = do_ubiformat(fb, mtd, NULL, NULL, 0);
+				if (ret)
+					goto out;
+			}
+
+			ret = ubiformat_write(mtd, buf, retlen, pos);
+			if (ret)
+				goto out;
+		} else {
+			pos = lseek(fd, pos, SEEK_SET);
+			if (pos == -1) {
+				ret = -errno;
+				goto out;
+			}
+
+			ret = write_full(fd, buf, retlen);
+			if (ret < 0)
+				goto out;
+		}
+	}
+
+	ret = 0;
+
+out:
+	free(buf);
+	sparse_image_close(sparse);
+out_close_fd:
+	close(fd);
+
+	return ret;
+}
+
+static void cb_flash(struct fastboot *fb, const char *cmd)
+{
+	struct file_list_entry *fentry;
+	int ret;
+	const char *filename = NULL, *sourcefile;
+	enum filetype filetype;
+
+	if (fastboot_download_to_buf(fb)) {
+		sourcefile = NULL;
+		filetype = file_detect_type(fb->buf, fb->download_bytes);
+	} else {
+		sourcefile = FASTBOOT_TMPFILE;
+		filetype = file_name_detect_type(FASTBOOT_TMPFILE);
+	}
+
+	fastboot_tx_print(fb, FASTBOOT_MSG_INFO, "Copying file to %s...",
+			  cmd);
+
+	fentry = file_list_entry_by_name(fb->files, cmd);
+
+	if (!fentry) {
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL, "No such partition: %s",
+				  cmd);
+		ret = -ENOENT;
+		goto out;
+	}
+
+	if (fb->cmd_flash) {
+		ret = fb->cmd_flash(fb, fentry, sourcefile, fb->buf,
+				      fb->download_size);
+		if (ret != FASTBOOT_CMD_FALLTHROUGH)
+			goto out;
+	}
+
+	filename = fentry->filename;
+
+	if (filetype == filetype_android_sparse) {
+		if (!IS_ENABLED(CONFIG_FASTBOOT_SPARSE) ||
+		    fastboot_download_to_buf(fb)) {
+			fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+					  "sparse image not supported");
+			ret = -EOPNOTSUPP;
+			goto out;
+		}
+
+		ret = fastboot_handle_sparse(fb, fentry);
+		if (ret)
+			fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+					  "writing sparse image: %s",
+					  strerror(-ret));
+
+		goto out;
+	}
+
+	ret = check_ubi(fb, fentry, filetype);
+	if (ret < 0)
+		goto out;
+
+	if (ret > 0) {
+		struct mtd_info *mtd;
+
+		mtd = get_mtd(fb, fentry->filename);
+
+		ret = do_ubiformat(fb, mtd, sourcefile, fb->buf,
+				   fb->download_size);
+		if (ret) {
+			fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+					  "write partition: %s",
+					  strerror(-ret));
+			goto out;
+		}
+
+		goto out;
+	}
+
+	if (IS_ENABLED(CONFIG_BAREBOX_UPDATE) && filetype_is_barebox_image(filetype)) {
+		struct bbu_handler *handler;
+		struct bbu_data data = {
+			.devicefile = filename,
+			.flags = BBU_FLAG_YES,
+		};
+
+		handler = bbu_find_handler_by_device(data.devicefile);
+		if (!handler)
+			goto copy;
+
+		fastboot_tx_print(fb, FASTBOOT_MSG_INFO,
+				  "This is a barebox image...");
+
+		if (fastboot_download_to_buf(fb)) {
+			data.len = fb->download_size;
+		} else {
+			ret = read_file_2(sourcefile, &data.len, &fb->buf,
+					fb->download_size);
+			if (ret) {
+				fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+						  "reading barebox");
+				goto out;
+			}
+		}
+
+		data.image = fb->buf;
+		data.imagefile = sourcefile;
+
+		ret = barebox_update(&data, handler);
+
+		if (ret)
+			fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+				  "update barebox: %s", strerror(-ret));
+
+		goto out;
+	}
+
+copy:
+	if (fastboot_download_to_buf(fb))
+		ret = write_file(filename, fb->buf, fb->download_size);
+	else
+		ret = copy_file(FASTBOOT_TMPFILE, filename, 1);
+
+	if (ret)
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+				  "write partition: %s", strerror(-ret));
+
+out:
+	if (!ret)
+		fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, "");
+
+	free(fb->buf);
+	fb->buf = NULL;
+
+	if (!fastboot_download_to_buf(fb))
+		unlink(FASTBOOT_TMPFILE);
+}
+
+static void cb_erase(struct fastboot *fb, const char *cmd)
+{
+	struct file_list_entry *fentry;
+	int ret;
+	const char *filename = NULL;
+	int fd;
+
+	fastboot_tx_print(fb, FASTBOOT_MSG_INFO, "Erasing %s...", cmd);
+
+	file_list_for_each_entry(fb->files, fentry) {
+		if (!strcmp(cmd, fentry->name)) {
+			filename = fentry->filename;
+			break;
+		}
+	}
+
+	if (!filename) {
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+				  "No such partition: %s", cmd);
+		return;
+	}
+
+	fd = open(filename, O_RDWR);
+	if (fd < 0)
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL, strerror(-fd));
+
+	ret = erase(fd, ERASE_SIZE_ALL, 0);
+
+	close(fd);
+
+	if (ret)
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+				  "cannot erase partition %s: %s",
+				  filename, strerror(-ret));
+	else
+		fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, "");
+}
+
+struct cmd_dispatch_info {
+	char *cmd;
+	void (*cb)(struct fastboot *fb, const char *opt);
+};
+
+static void fb_run_command(struct fastboot *fb, const char *cmdbuf,
+		const struct cmd_dispatch_info *cmds, int num_commands)
+{
+	const struct cmd_dispatch_info *cmd;
+	int i;
+
+	console_countdown_abort();
+
+	for (i = 0; i < num_commands; i++) {
+		cmd = &cmds[i];
+
+		if (!strcmp_l1(cmd->cmd, cmdbuf)) {
+			cmd->cb(fb, cmdbuf + strlen(cmd->cmd));
+
+			return;
+		}
+	}
+
+	fastboot_tx_print(fb, FASTBOOT_MSG_FAIL, "unknown command %s",
+			  cmdbuf);
+}
+
+static void cb_oem_getenv(struct fastboot *fb, const char *cmd)
+{
+	const char *value;
+
+	pr_debug("%s: \"%s\"\n", __func__, cmd);
+
+	value = getenv(cmd);
+
+	fastboot_tx_print(fb, FASTBOOT_MSG_INFO, value ? value : "");
+	fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, "");
+}
+
+static void cb_oem_setenv(struct fastboot *fb, const char *cmd)
+{
+	char *var = xstrdup(cmd);
+	char *value;
+	int ret;
+
+	pr_debug("%s: \"%s\"\n", __func__, cmd);
+
+	value = strchr(var, '=');
+	if (!value) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	*value++ = 0;
+
+	ret = setenv(var, value);
+	if (ret)
+		goto out;
+
+	fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, "");
+out:
+	free(var);
+
+	if (ret)
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL, strerror(-ret));
+}
+
+static void cb_oem_exec(struct fastboot *fb, const char *cmd)
+{
+	int ret;
+
+	if (!IS_ENABLED(CONFIG_COMMAND_SUPPORT)) {
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL,
+				  "no command support available");
+		return;
+	}
+
+	ret = run_command(cmd);
+	if (ret < 0)
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL, strerror(-ret));
+	else if (ret > 0)
+		fastboot_tx_print(fb, FASTBOOT_MSG_FAIL, "");
+	else
+		fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, "");
+}
+
+static const struct cmd_dispatch_info cmd_oem_dispatch_info[] = {
+	{
+		.cmd = "getenv ",
+		.cb = cb_oem_getenv,
+	}, {
+		.cmd = "setenv ",
+		.cb = cb_oem_setenv,
+	}, {
+		.cmd = "exec ",
+		.cb = cb_oem_exec,
+	},
+};
+
+static void __maybe_unused cb_oem(struct fastboot *fb, const char *cmd)
+{
+	pr_debug("%s: \"%s\"\n", __func__, cmd);
+
+	fb_run_command(fb, cmd, cmd_oem_dispatch_info, ARRAY_SIZE(cmd_oem_dispatch_info));
+}
+
+static const struct cmd_dispatch_info cmd_dispatch_info[] = {
+	{
+		.cmd = "reboot",
+		.cb = cb_reboot,
+	}, {
+		.cmd = "getvar:",
+		.cb = cb_getvar,
+	}, {
+		.cmd = "download:",
+		.cb = cb_download,
+#if defined(CONFIG_BOOTM)
+	}, {
+		.cmd = "boot",
+		.cb = cb_boot,
+#endif
+	}, {
+		.cmd = "flash:",
+		.cb = cb_flash,
+	}, {
+		.cmd = "erase:",
+		.cb = cb_erase,
+#if defined(CONFIG_FASTBOOT_CMD_OEM)
+	}, {
+		.cmd = "oem ",
+		.cb = cb_oem,
+#endif
+	},
+};
+
+void fastboot_exec_cmd(struct fastboot *fb, const char *cmdbuf)
+{
+	int ret;
+
+	g_fb = fb;
+	fb->active = true;
+
+	if (fb->cmd_exec) {
+		ret = fb->cmd_exec(fb, cmdbuf);
+		if (ret != FASTBOOT_CMD_FALLTHROUGH)
+			return;
+	}
+
+	fb_run_command(fb, cmdbuf, cmd_dispatch_info,
+				ARRAY_SIZE(cmd_dispatch_info));
+}
+
+static int fastboot_globalvars_init(void)
+{
+	if (IS_ENABLED(CONFIG_FASTBOOT_SPARSE))
+		globalvar_add_simple_int("usbgadget.fastboot_max_download_size",
+				 &fastboot_max_download_size, "%u");
+
+	return 0;
+}
+
+device_initcall(fastboot_globalvars_init);
+
+BAREBOX_MAGICVAR_NAMED(global_usbgadget_fastboot_max_download_size,
+		       global.usbgadget.fastboot_max_download_size,
+		       "Fastboot maximum download size");
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 6e60c7aee..ddae24570 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -57,7 +57,8 @@ config USB_GADGET_FASTBOOT
 	bool
 	select BANNER
 	select FILE_LIST
-	prompt "Android Fastboot support"
+	select FASTBOOT_BASE
+	prompt "Android Fastboot USB Gadget"
 
 config USB_GADGET_FASTBOOT_SPARSE
 	bool
diff --git a/drivers/usb/gadget/f_fastboot.c b/drivers/usb/gadget/f_fastboot.c
index 2d760867a..f8a9c3253 100644
--- a/drivers/usb/gadget/f_fastboot.c
+++ b/drivers/usb/gadget/f_fastboot.c
@@ -11,90 +11,36 @@
  * Copyright 2014 Sascha Hauer <s.hauer@pengutronix.de>
  * Ported to barebox
  *
+ * Copyright 2020 Edmund Henniges <eh@emlix.com>
+ * Copyright 2020 Daniel Glöckner <dg@emlix.com>
+ * Split off of generic parts
+ *
  * SPDX-License-Identifier:	GPL-2.0+
  */
 
 #define pr_fmt(fmt) "fastboot: " fmt
 
-#include <common.h>
-#include <command.h>
-#include <errno.h>
-#include <malloc.h>
-#include <fcntl.h>
-#include <clock.h>
-#include <ioctl.h>
-#include <libbb.h>
-#include <bbu.h>
-#include <bootm.h>
 #include <dma.h>
-#include <fs.h>
-#include <libfile.h>
-#include <ubiformat.h>
-#include <stdlib.h>
-#include <file-list.h>
-#include <magicvar.h>
-#include <linux/sizes.h>
+#include <unistd.h>
 #include <progress.h>
-#include <environment.h>
-#include <globalvar.h>
-#include <restart.h>
-#include <console_countdown.h>
-#include <image-sparse.h>
-#include <usb/ch9.h>
-#include <usb/gadget.h>
+#include <fastboot.h>
 #include <usb/fastboot.h>
-#include <usb/composite.h>
-#include <linux/err.h>
-#include <linux/compiler.h>
-#include <linux/stat.h>
-#include <linux/mtd/mtd-abi.h>
-#include <linux/mtd/mtd.h>
-
-#define FASTBOOT_VERSION		"0.4"
 
 #define FASTBOOT_INTERFACE_CLASS	0xff
 #define FASTBOOT_INTERFACE_SUB_CLASS	0x42
 #define FASTBOOT_INTERFACE_PROTOCOL	0x03
 
-#define FASTBOOT_TMPFILE		"/.fastboot.img"
-
 #define EP_BUFFER_SIZE			4096
 
-static unsigned int fastboot_max_download_size = SZ_8M;
-
-struct fb_variable {
-	char *name;
-	char *value;
-	struct list_head list;
-};
-
 struct f_fastboot {
+	struct fastboot fastboot;
 	struct usb_function func;
 
 	/* IN/OUT EP's and corresponding requests */
 	struct usb_ep *in_ep, *out_ep;
 	struct usb_request *in_req, *out_req;
-	struct file_list *files;
-	int (*cmd_exec)(struct f_fastboot *, const char *cmd);
-	int (*cmd_flash)(struct f_fastboot *, struct file_list_entry *entry,
-			 const char *filename, const void *buf, size_t len);
-	int download_fd;
-	void *buf;
-	bool active;
-
-	size_t download_bytes;
-	size_t download_size;
-	struct list_head variables;
 };
 
-static inline bool fastboot_download_to_buf(struct f_fastboot *f_fb)
-{
-	if (IS_ENABLED(CONFIG_USB_GADGET_FASTBOOT_BUF))
-		return true;
-	else
-		return false;
-}
-
 static inline struct f_fastboot *func_to_fastboot(struct usb_function *f)
 {
 	return container_of(f, struct f_fastboot, func);
@@ -182,6 +128,9 @@ static struct usb_gadget_strings *fastboot_strings[] = {
 };
 
 static void rx_handler_command(struct usb_ep *ep, struct usb_request *req);
+static int fastboot_write_usb(struct fastboot *fb, const char *buffer,
+			      unsigned int buffer_size);
+static void fastboot_start_download_usb(struct fastboot *fb);
 
 static void fastboot_complete(struct usb_ep *ep, struct usb_request *req)
 {
@@ -206,116 +155,6 @@ static struct usb_request *fastboot_alloc_request(struct usb_ep *ep)
 	return req;
 }
 
-static void fb_setvar(struct fb_variable *var, const char *fmt, ...)
-{
-	va_list ap;
-
-	va_start(ap, fmt);
-	var->value = bvasprintf(fmt, ap);
-	va_end(ap);
-}
-
-static struct fb_variable *fb_addvar(struct f_fastboot *f_fb, const char *fmt, ...)
-{
-	struct fb_variable *var = xzalloc(sizeof(*var));
-	va_list ap;
-
-	va_start(ap, fmt);
-	var->name = bvasprintf(fmt, ap);
-	va_end(ap);
-
-	list_add_tail(&var->list, &f_fb->variables);
-
-	return var;
-}
-
-static int fastboot_add_partition_variables(struct f_fastboot *f_fb,
-		struct file_list_entry *fentry)
-{
-	struct stat s;
-	size_t size = 0;
-	int fd, ret;
-	struct mtd_info_user mtdinfo;
-	char *type = NULL;
-	struct fb_variable *var;
-
-	ret = stat(fentry->filename, &s);
-	if (ret) {
-		device_detect_by_name(devpath_to_name(fentry->filename));
-		ret = stat(fentry->filename, &s);
-	}
-
-	if (ret) {
-		if (fentry->flags & FILE_LIST_FLAG_CREATE) {
-			ret = 0;
-			type = "file";
-			goto out;
-		}
-
-		goto out;
-	}
-
-	fd = open(fentry->filename, O_RDWR);
-	if (fd < 0) {
-		ret = -EINVAL;
-		goto out;
-	}
-
-	size = s.st_size;
-
-	ret = ioctl(fd, MEMGETINFO, &mtdinfo);
-
-	close(fd);
-
-	if (!ret) {
-		switch (mtdinfo.type) {
-		case MTD_NANDFLASH:
-			type = "NAND-flash";
-			break;
-		case MTD_NORFLASH:
-			type = "NOR-flash";
-			break;
-		case MTD_UBIVOLUME:
-			type = "UBI";
-			break;
-		default:
-			type = "flash";
-			break;
-		}
-
-		goto out;
-	}
-
-	type = "basic";
-	ret = 0;
-
-out:
-	if (ret)
-		return ret;
-
-	var = fb_addvar(f_fb, "partition-size:%s", fentry->name);
-	fb_setvar(var, "%08zx", size);
-	var = fb_addvar(f_fb, "partition-type:%s", fentry->name);
-	fb_setvar(var, "%s", type);
-
-	return ret;
-}
-
-static int fastboot_add_bbu_variables(struct bbu_handler *handler, void *ctx)
-{
-	struct f_fastboot *f_fb = ctx;
-	char *name;
-	int ret;
-
-	name = basprintf("bbu-%s", handler->name);
-
-	ret = file_list_add_entry(f_fb->files, name, handler->devicefile, 0);
-
-	free(name);
-
-	return ret;
-}
-
 static int fastboot_bind(struct usb_configuration *c, struct usb_function *f)
 {
 	struct usb_composite_dev *cdev = c->cdev;
@@ -325,30 +164,17 @@ static int fastboot_bind(struct usb_configuration *c, struct usb_function *f)
 	struct usb_string *us;
 	const struct usb_function_instance *fi = f->fi;
 	struct f_fastboot_opts *opts = container_of(fi, struct f_fastboot_opts, func_inst);
-	struct file_list_entry *fentry;
-	struct fb_variable *var;
-
-	f_fb->files = opts->files;
-	f_fb->cmd_exec = opts->cmd_exec;
-	f_fb->cmd_flash = opts->cmd_flash;
-
-	var = fb_addvar(f_fb, "version");
-	fb_setvar(var, "0.4");
-	var = fb_addvar(f_fb, "bootloader-version");
-	fb_setvar(var, release_string);
-	if (IS_ENABLED(CONFIG_USB_GADGET_FASTBOOT_SPARSE)) {
-		var = fb_addvar(f_fb, "max-download-size");
-		fb_setvar(var, "%u", fastboot_max_download_size);
-	}
 
-	if (IS_ENABLED(CONFIG_BAREBOX_UPDATE) && opts->export_bbu)
-		bbu_handlers_iterate(fastboot_add_bbu_variables, f_fb);
+	f_fb->fastboot.write = fastboot_write_usb;
+	f_fb->fastboot.start_download = fastboot_start_download_usb;
 
-	file_list_for_each_entry(f_fb->files, fentry) {
-		ret = fastboot_add_partition_variables(f_fb, fentry);
-		if (ret)
-			return ret;
-	}
+	f_fb->fastboot.files = opts->common.files;
+	f_fb->fastboot.cmd_exec = opts->common.cmd_exec;
+	f_fb->fastboot.cmd_flash = opts->common.cmd_flash;
+
+	ret = fastboot_generic_init(&f_fb->fastboot, opts->common.export_bbu);
+	if (ret)
+		return ret;
 
 	/* DYNAMIC interface numbers assignments */
 	id = usb_interface_id(c, f);
@@ -409,7 +235,6 @@ static int fastboot_bind(struct usb_configuration *c, struct usb_function *f)
 static void fastboot_unbind(struct usb_configuration *c, struct usb_function *f)
 {
 	struct f_fastboot *f_fb = func_to_fastboot(f);
-	struct fb_variable *var, *tmp;
 
 	usb_ep_dequeue(f_fb->in_ep, f_fb->in_req);
 	free(f_fb->in_req->buf);
@@ -421,14 +246,7 @@ static void fastboot_unbind(struct usb_configuration *c, struct usb_function *f)
 	usb_ep_free_request(f_fb->out_ep, f_fb->out_req);
 	f_fb->out_req = NULL;
 
-	list_for_each_entry_safe(var, tmp, &f_fb->variables, list) {
-		free(var->name);
-		free(var->value);
-		list_del(&var->list);
-		free(var);
-	}
-
-	f_fb->active = false;
+	fastboot_generic_free(&f_fb->fastboot);
 }
 
 static void fastboot_disable(struct usb_function *f)
@@ -481,43 +299,21 @@ err:
 	return ret;
 }
 
-static struct f_fastboot *g_f_fb;
-
 static void fastboot_free_func(struct usb_function *f)
 {
 	struct f_fastboot *f_fb = container_of(f, struct f_fastboot, func);
 
-	if (g_f_fb == f_fb)
-		g_f_fb = NULL;
-
+	fastboot_generic_close(&f_fb->fastboot);
 	free(f_fb);
 }
 
-/*
- * A "oem exec bootm" or similar commands will stop barebox. Tell the
- * fastboot command on the other side so that it doesn't run into a
- * timeout.
- */
-static void fastboot_shutdown(void)
-{
-	struct f_fastboot *f_fb = g_f_fb;
-
-	if (!f_fb || !f_fb->active)
-		return;
-
-	fastboot_tx_print(f_fb, FASTBOOT_MSG_INFO, "barebox shutting down");
-	fastboot_tx_print(f_fb, FASTBOOT_MSG_OKAY, "");
-}
-
-early_exitcall(fastboot_shutdown);
-
 static struct usb_function *fastboot_alloc_func(struct usb_function_instance *fi)
 {
 	struct f_fastboot *f_fb;
 
 	f_fb = xzalloc(sizeof(*f_fb));
 
-	INIT_LIST_HEAD(&f_fb->variables);
+	INIT_LIST_HEAD(&f_fb->fastboot.variables);
 
 	f_fb->func.name = "fastboot";
 	f_fb->func.strings = fastboot_strings;
@@ -527,9 +323,6 @@ static struct usb_function *fastboot_alloc_func(struct usb_function_instance *fi
 	f_fb->func.unbind = fastboot_unbind;
 	f_fb->func.free_func = fastboot_free_func;
 
-	if (!g_f_fb)
-		g_f_fb = f_fb;
-
 	return &f_fb->func;
 }
 
@@ -553,8 +346,9 @@ static struct usb_function_instance *fastboot_alloc_instance(void)
 
 DECLARE_USB_FUNCTION_INIT(fastboot, fastboot_alloc_instance, fastboot_alloc_func);
 
-static int fastboot_tx_write(struct f_fastboot *f_fb, const char *buffer, unsigned int buffer_size)
+static int fastboot_write_usb(struct fastboot *fb, const char *buffer, unsigned int buffer_size)
 {
+	struct f_fastboot *f_fb = container_of(fb, struct f_fastboot, fastboot);
 	struct usb_request *in_req = f_fb->in_req;
 	uint64_t start;
 	int ret;
@@ -580,91 +374,10 @@ static int fastboot_tx_write(struct f_fastboot *f_fb, const char *buffer, unsign
 	return 0;
 }
 
-static char *fastboot_msg[] = {
-	[FASTBOOT_MSG_OKAY] = "OKAY",
-	[FASTBOOT_MSG_FAIL] = "FAIL",
-	[FASTBOOT_MSG_INFO] = "INFO",
-	[FASTBOOT_MSG_DATA] = "DATA",
-};
-
-int fastboot_tx_print(struct f_fastboot *f_fb, enum fastboot_msg_type type,
-		      const char *fmt, ...)
-{
-	struct va_format vaf;
-	char buf[64];
-	va_list ap;
-	int n;
-	const char *msg = fastboot_msg[type];
-
-	va_start(ap, fmt);
-	vaf.fmt = fmt;
-	vaf.va = &ap;
-
-	n = snprintf(buf, 64, "%s%pV", msg, &vaf);
-
-	switch (type) {
-	case FASTBOOT_MSG_OKAY:
-		f_fb->active = false;
-		break;
-	case FASTBOOT_MSG_FAIL:
-		f_fb->active = false;
-		pr_err("%pV\n", &vaf);
-		break;
-	case FASTBOOT_MSG_INFO:
-		pr_info("%pV\n", &vaf);
-		break;
-	case FASTBOOT_MSG_DATA:
-		break;
-	}
-
-	va_end(ap);
-
-	if (n > 64)
-		n = 64;
-
-	return fastboot_tx_write(f_fb, buf, n);
-}
-
-static void cb_reboot(struct f_fastboot *f_fb, const char *cmd)
-{
-	restart_machine();
-}
-
-static int strcmp_l1(const char *s1, const char *s2)
-{
-	if (!s1 || !s2)
-		return -1;
-	return strncmp(s1, s2, strlen(s1));
-}
-
-static void cb_getvar(struct f_fastboot *f_fb, const char *cmd)
-{
-	struct fb_variable *var;
-
-	pr_debug("getvar: \"%s\"\n", cmd);
-
-	if (!strcmp_l1(cmd, "all")) {
-		list_for_each_entry(var, &f_fb->variables, list) {
-			fastboot_tx_print(f_fb, FASTBOOT_MSG_INFO, "%s: %s",
-					  var->name, var->value);
-		}
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_OKAY, "");
-		return;
-	}
-
-	list_for_each_entry(var, &f_fb->variables, list) {
-		if (!strcmp(cmd, var->name)) {
-			fastboot_tx_print(f_fb, FASTBOOT_MSG_OKAY, var->value);
-			return;
-		}
-	}
-
-	fastboot_tx_print(f_fb, FASTBOOT_MSG_OKAY, "");
-}
-
 static int rx_bytes_expected(struct f_fastboot *f_fb)
 {
-	int remaining = f_fb->download_size - f_fb->download_bytes;
+	int remaining = f_fb->fastboot.download_size
+		      - f_fb->fastboot.download_bytes;
 
 	if (remaining >= EP_BUFFER_SIZE)
 		return EP_BUFFER_SIZE;
@@ -683,649 +396,50 @@ static void rx_handler_dl_image(struct usb_ep *ep, struct usb_request *req)
 		return;
 	}
 
-	if (fastboot_download_to_buf(f_fb)) {
-		memcpy(f_fb->buf + f_fb->download_bytes, buffer, req->actual);
-	} else {
-		ret = write(f_fb->download_fd, buffer, req->actual);
-		if (ret < 0) {
-			fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL, strerror(-ret));
-			return;
-		}
+	ret = fastboot_handle_download_data(&f_fb->fastboot, buffer,
+					    req->actual);
+	if (ret < 0) {
+		fastboot_tx_print(&f_fb->fastboot, FASTBOOT_MSG_FAIL,
+				  strerror(-ret));
+		return;
 	}
 
-	f_fb->download_bytes += req->actual;
-
 	req->length = rx_bytes_expected(f_fb);
 
-	show_progress(f_fb->download_bytes);
-
 	/* Check if transfer is done */
-	if (f_fb->download_bytes >= f_fb->download_size) {
+	if (f_fb->fastboot.download_bytes >= f_fb->fastboot.download_size) {
 		req->complete = rx_handler_command;
 		req->length = EP_BUFFER_SIZE;
-		close(f_fb->download_fd);
-
-		printf("\n");
 
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_INFO, "Downloading %d bytes finished",
-				  f_fb->download_bytes);
-
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_OKAY, "");
+		fastboot_download_finished(&f_fb->fastboot);
 	}
 
 	req->actual = 0;
 	usb_ep_queue(ep, req);
 }
 
-static void cb_download(struct f_fastboot *f_fb, const char *cmd)
-{
-	f_fb->download_size = simple_strtoul(cmd, NULL, 16);
-	f_fb->download_bytes = 0;
-
-	fastboot_tx_print(f_fb, FASTBOOT_MSG_INFO, "Downloading %d bytes...",
-			  f_fb->download_size);
-
-	init_progression_bar(f_fb->download_size);
-
-	if (fastboot_download_to_buf(f_fb)) {
-		free(f_fb->buf);
-		f_fb->buf = malloc(f_fb->download_size);
-		if (!f_fb->buf) {
-			fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-					  "not enough memory");
-			return;
-		}
-	} else {
-		f_fb->download_fd = open(FASTBOOT_TMPFILE, O_WRONLY | O_CREAT | O_TRUNC);
-		if (f_fb->download_fd < 0) {
-			fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-					  "internal error");
-			return;
-		}
-	}
-
-	if (!f_fb->download_size) {
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-					  "data invalid size");
-	} else {
-		struct usb_request *req = f_fb->out_req;
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_DATA,
-					  "%08x", f_fb->download_size);
-		req->complete = rx_handler_dl_image;
-		req->length = rx_bytes_expected(f_fb);
-	}
-}
-
-static void __maybe_unused cb_boot(struct f_fastboot *f_fb, const char *opt)
-{
-	int ret;
-	struct bootm_data data = {
-		.initrd_address = UIMAGE_INVALID_ADDRESS,
-		.os_address = UIMAGE_SOME_ADDRESS,
-	};
-
-	fastboot_tx_print(f_fb, FASTBOOT_MSG_INFO, "Booting kernel..\n");
-
-	globalvar_set_match("linux.bootargs.dyn.", "");
-	globalvar_set_match("bootm.image", "");
-
-	data.os_file = FASTBOOT_TMPFILE;
-
-	ret = bootm_boot(&data);
-
-	if (ret)
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL, "Booting failed: %s",
-				   strerror(-ret));
-	else
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_OKAY, "");
-}
-
-static struct mtd_info *get_mtd(struct f_fastboot *f_fb, const char *filename)
-{
-	int fd, ret;
-	struct mtd_info_user meminfo;
-
-	fd = open(filename, O_RDONLY);
-	if (fd < 0)
-		return ERR_PTR(-errno);
-
-	ret = ioctl(fd, MEMGETINFO, &meminfo);
-
-	close(fd);
-
-	if (ret)
-		return ERR_PTR(ret);
-
-	return meminfo.mtd;
-}
-
-static int do_ubiformat(struct f_fastboot *f_fb, struct mtd_info *mtd,
-			const char *file, const void *buf, size_t len)
-{
-	struct ubiformat_args args = {
-		.yes = 1,
-		.image = file,
-		.image_buf = buf,
-		.image_size = len,
-	};
-
-	if (!file)
-		args.novtbl = 1;
-
-	if (!IS_ENABLED(CONFIG_UBIFORMAT)) {
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-					  "ubiformat is not available");
-		return -ENODEV;
-	}
-
-	return ubiformat(mtd, &args);
-}
-
-
-static int check_ubi(struct f_fastboot *f_fb, struct file_list_entry *fentry,
-		     enum filetype filetype)
-{
-	struct mtd_info *mtd;
-
-	mtd = get_mtd(f_fb, fentry->filename);
-
-	/*
-	 * Issue a warning when we are about to write a UBI image to a MTD device
-	 * and the FILE_LIST_FLAG_UBI is not given as this means we loose all
-	 * erase counters.
-	 */
-	if (!IS_ERR(mtd) && filetype == filetype_ubi &&
-	    !(fentry->flags & FILE_LIST_FLAG_UBI)) {
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_INFO,
-				  "writing UBI image to MTD device, "
-				  "add the 'u' ");
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_INFO,
-				  "flag to the partition description");
-		return 0;
-	}
-
-	if (!(fentry->flags & FILE_LIST_FLAG_UBI))
-		return 0;
-
-	if (!IS_ENABLED(CONFIG_UBIFORMAT)) {
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-				  "ubiformat not available");
-		return -ENOSYS;
-	}
-
-	if (IS_ERR(mtd)) {
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-				  "UBI flag given on non-MTD device");
-		return -EINVAL;
-	}
-
-	if (filetype == filetype_ubi) {
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_INFO,
-				  "This is a UBI image...");
-		return 1;
-	} else {
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-				  "This is no UBI image but %s",
-				 file_type_to_string(filetype));
-		return -EINVAL;
-	}
-}
-
-static int fastboot_handle_sparse(struct f_fastboot *f_fb,
-				  struct file_list_entry *fentry)
-{
-	struct sparse_image_ctx *sparse;
-	void *buf = NULL;
-	int ret, fd;
-	unsigned int flags = O_RDWR;
-	int bufsiz = SZ_128K;
-	struct stat s;
-	struct mtd_info *mtd = NULL;
-
-	ret = stat(fentry->filename, &s);
-	if (ret) {
-		if (fentry->flags & FILE_LIST_FLAG_CREATE)
-			flags |= O_CREAT;
-		else
-			return ret;
-	}
-
-	fd = open(fentry->filename, flags);
-	if (fd < 0)
-		return -errno;
-
-	ret = fstat(fd, &s);
-	if (ret)
-		goto out_close_fd;
-
-	sparse = sparse_image_open(FASTBOOT_TMPFILE);
-	if (IS_ERR(sparse)) {
-		pr_err("Cannot open sparse image\n");
-		ret = PTR_ERR(sparse);
-		goto out_close_fd;
-	}
-
-	if (S_ISREG(s.st_mode)) {
-		ret = ftruncate(fd, sparse_image_size(sparse));
-		if (ret)
-			goto out;
-	}
-
-	buf = malloc(bufsiz);
-	if (!buf) {
-		ret = -ENOMEM;
-		goto out;
-	}
-
-	if (fentry->flags & FILE_LIST_FLAG_UBI) {
-		mtd = get_mtd(f_fb, fentry->filename);
-		if (IS_ERR(mtd)) {
-			ret = PTR_ERR(mtd);
-			goto out;
-		}
-	}
-
-	while (1) {
-		int retlen;
-		loff_t pos;
-
-		ret = sparse_image_read(sparse, buf, &pos, bufsiz, &retlen);
-		if (ret)
-			goto out;
-		if (!retlen)
-			break;
-
-		if (pos == 0) {
-			ret = check_ubi(f_fb, fentry, file_detect_type(buf, retlen));
-			if (ret < 0)
-				goto out;
-		}
-
-		if (fentry->flags & FILE_LIST_FLAG_UBI) {
-			if (!IS_ENABLED(CONFIG_UBIFORMAT)) {
-				ret = -ENOSYS;
-				goto out;
-			}
-
-			if (pos == 0) {
-				ret = do_ubiformat(f_fb, mtd, NULL, NULL, 0);
-				if (ret)
-					goto out;
-			}
-
-			ret = ubiformat_write(mtd, buf, retlen, pos);
-			if (ret)
-				goto out;
-		} else {
-			pos = lseek(fd, pos, SEEK_SET);
-			if (pos == -1) {
-				ret = -errno;
-				goto out;
-			}
-
-			ret = write_full(fd, buf, retlen);
-			if (ret < 0)
-				goto out;
-		}
-	}
-
-	ret = 0;
-
-out:
-	free(buf);
-	sparse_image_close(sparse);
-out_close_fd:
-	close(fd);
-
-	return ret;
-}
-
-static void cb_flash(struct f_fastboot *f_fb, const char *cmd)
-{
-	struct file_list_entry *fentry;
-	int ret;
-	const char *filename = NULL, *sourcefile;
-	enum filetype filetype;
-
-	if (fastboot_download_to_buf(f_fb)) {
-		sourcefile = NULL;
-		filetype = file_detect_type(f_fb->buf, f_fb->download_bytes);
-	} else {
-		sourcefile = FASTBOOT_TMPFILE;
-		filetype = file_name_detect_type(FASTBOOT_TMPFILE);
-	}
-
-	fastboot_tx_print(f_fb, FASTBOOT_MSG_INFO, "Copying file to %s...",
-			  cmd);
-
-	fentry = file_list_entry_by_name(f_fb->files, cmd);
-
-	if (!fentry) {
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL, "No such partition: %s",
-				  cmd);
-		ret = -ENOENT;
-		goto out;
-	}
-
-	if (f_fb->cmd_flash) {
-		ret = f_fb->cmd_flash(f_fb, fentry, sourcefile, f_fb->buf,
-				      f_fb->download_size);
-		if (ret != FASTBOOT_CMD_FALLTHROUGH)
-			goto out;
-	}
-
-	filename = fentry->filename;
-
-	if (filetype == filetype_android_sparse) {
-		if (!IS_ENABLED(CONFIG_USB_GADGET_FASTBOOT_SPARSE) ||
-		    fastboot_download_to_buf(f_fb)) {
-			fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-					  "sparse image not supported");
-			ret = -EOPNOTSUPP;
-			goto out;
-		}
-
-		ret = fastboot_handle_sparse(f_fb, fentry);
-		if (ret)
-			fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-					  "writing sparse image: %s",
-					  strerror(-ret));
-
-		goto out;
-	}
-
-	ret = check_ubi(f_fb, fentry, filetype);
-	if (ret < 0)
-		goto out;
-
-	if (ret > 0) {
-		struct mtd_info *mtd;
-
-		mtd = get_mtd(f_fb, fentry->filename);
-
-		ret = do_ubiformat(f_fb, mtd, sourcefile, f_fb->buf,
-				   f_fb->download_size);
-		if (ret) {
-			fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-					  "write partition: %s",
-					  strerror(-ret));
-			goto out;
-		}
-
-		goto out;
-	}
-
-	if (IS_ENABLED(CONFIG_BAREBOX_UPDATE) && filetype_is_barebox_image(filetype)) {
-		struct bbu_handler *handler;
-		struct bbu_data data = {
-			.devicefile = filename,
-			.flags = BBU_FLAG_YES,
-		};
-
-		handler = bbu_find_handler_by_device(data.devicefile);
-		if (!handler)
-			goto copy;
-
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_INFO,
-				  "This is a barebox image...");
-
-		if (fastboot_download_to_buf(f_fb)) {
-			data.len = f_fb->download_size;
-		} else {
-			ret = read_file_2(sourcefile, &data.len, &f_fb->buf,
-					f_fb->download_size);
-			if (ret) {
-				fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-						  "reading barebox");
-				goto out;
-			}
-		}
-
-		data.image = f_fb->buf;
-		data.imagefile = sourcefile;
-
-		ret = barebox_update(&data, handler);
-
-		if (ret)
-			fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-				  "update barebox: %s", strerror(-ret));
-
-		goto out;
-	}
-
-copy:
-	if (fastboot_download_to_buf(f_fb))
-		ret = write_file(filename, f_fb->buf, f_fb->download_size);
-	else
-		ret = copy_file(FASTBOOT_TMPFILE, filename, 1);
-
-	if (ret)
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-				  "write partition: %s", strerror(-ret));
-
-out:
-	if (!ret)
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_OKAY, "");
-
-	free(f_fb->buf);
-	f_fb->buf = NULL;
-
-	if (!fastboot_download_to_buf(f_fb))
-		unlink(FASTBOOT_TMPFILE);
-}
-
-static void cb_erase(struct f_fastboot *f_fb, const char *cmd)
-{
-	struct file_list_entry *fentry;
-	int ret;
-	const char *filename = NULL;
-	int fd;
-
-	fastboot_tx_print(f_fb, FASTBOOT_MSG_INFO, "Erasing %s...", cmd);
-
-	file_list_for_each_entry(f_fb->files, fentry) {
-		if (!strcmp(cmd, fentry->name)) {
-			filename = fentry->filename;
-			break;
-		}
-	}
-
-	if (!filename) {
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-				  "No such partition: %s", cmd);
-		return;
-	}
-
-	fd = open(filename, O_RDWR);
-	if (fd < 0)
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL, strerror(-fd));
-
-	ret = erase(fd, ERASE_SIZE_ALL, 0);
-
-	close(fd);
-
-	if (ret)
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-				  "cannot erase partition %s: %s",
-				  filename, strerror(-ret));
-	else
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_OKAY, "");
-}
-
-struct cmd_dispatch_info {
-	char *cmd;
-	void (*cb)(struct f_fastboot *f_fb, const char *opt);
-};
-
-static void fb_run_command(struct f_fastboot *f_fb, const char *cmdbuf,
-		const struct cmd_dispatch_info *cmds, int num_commands)
-{
-	const struct cmd_dispatch_info *cmd;
-	int i;
-
-	console_countdown_abort();
-
-	for (i = 0; i < num_commands; i++) {
-		cmd = &cmds[i];
-
-		if (!strcmp_l1(cmd->cmd, cmdbuf)) {
-			cmd->cb(f_fb, cmdbuf + strlen(cmd->cmd));
-
-			return;
-		}
-	}
-
-	fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL, "unknown command %s",
-			  cmdbuf);
-}
-
-static void cb_oem_getenv(struct f_fastboot *f_fb, const char *cmd)
-{
-	const char *value;
-
-	pr_debug("%s: \"%s\"\n", __func__, cmd);
-
-	value = getenv(cmd);
-
-	fastboot_tx_print(f_fb, FASTBOOT_MSG_INFO, value ? value : "");
-	fastboot_tx_print(f_fb, FASTBOOT_MSG_OKAY, "");
-}
-
-static void cb_oem_setenv(struct f_fastboot *f_fb, const char *cmd)
-{
-	char *var = xstrdup(cmd);
-	char *value;
-	int ret;
-
-	pr_debug("%s: \"%s\"\n", __func__, cmd);
-
-	value = strchr(var, '=');
-	if (!value) {
-		ret = -EINVAL;
-		goto out;
-	}
-
-	*value++ = 0;
-
-	ret = setenv(var, value);
-	if (ret)
-		goto out;
-
-	fastboot_tx_print(f_fb, FASTBOOT_MSG_OKAY, "");
-out:
-	free(var);
-
-	if (ret)
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL, strerror(-ret));
-}
-
-static void cb_oem_exec(struct f_fastboot *f_fb, const char *cmd)
+static void fastboot_start_download_usb(struct fastboot *fb)
 {
-	int ret;
+	struct f_fastboot *f_fb = container_of(fb, struct f_fastboot, fastboot);
+	struct usb_request *req = f_fb->out_req;
 
-	if (!IS_ENABLED(CONFIG_COMMAND_SUPPORT)) {
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL,
-				  "no command support available");
-		return;
-	}
-
-	ret = run_command(cmd);
-	if (ret < 0)
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL, strerror(-ret));
-	else if (ret > 0)
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_FAIL, "");
-	else
-		fastboot_tx_print(f_fb, FASTBOOT_MSG_OKAY, "");
-}
-
-static const struct cmd_dispatch_info cmd_oem_dispatch_info[] = {
-	{
-		.cmd = "getenv ",
-		.cb = cb_oem_getenv,
-	}, {
-		.cmd = "setenv ",
-		.cb = cb_oem_setenv,
-	}, {
-		.cmd = "exec ",
-		.cb = cb_oem_exec,
-	},
-};
-
-static void __maybe_unused cb_oem(struct f_fastboot *f_fb, const char *cmd)
-{
-	pr_debug("%s: \"%s\"\n", __func__, cmd);
-
-	fb_run_command(f_fb, cmd, cmd_oem_dispatch_info, ARRAY_SIZE(cmd_oem_dispatch_info));
+	req->complete = rx_handler_dl_image;
+	req->length = rx_bytes_expected(f_fb);
+	fastboot_start_download_generic(fb);
 }
 
-static const struct cmd_dispatch_info cmd_dispatch_info[] = {
-	{
-		.cmd = "reboot",
-		.cb = cb_reboot,
-	}, {
-		.cmd = "getvar:",
-		.cb = cb_getvar,
-	}, {
-		.cmd = "download:",
-		.cb = cb_download,
-#if defined(CONFIG_BOOTM)
-	}, {
-		.cmd = "boot",
-		.cb = cb_boot,
-#endif
-	}, {
-		.cmd = "flash:",
-		.cb = cb_flash,
-	}, {
-		.cmd = "erase:",
-		.cb = cb_erase,
-#if defined(CONFIG_USB_GADGET_FASTBOOT_CMD_OEM)
-	}, {
-		.cmd = "oem ",
-		.cb = cb_oem,
-#endif
-	},
-};
-
 static void rx_handler_command(struct usb_ep *ep, struct usb_request *req)
 {
 	char *cmdbuf = req->buf;
 	struct f_fastboot *f_fb = req->context;
-	int ret;
 
 	if (req->status != 0)
 		return;
 
-	f_fb->active = true;
-
 	*(cmdbuf + req->actual) = 0;
-
-	if (f_fb->cmd_exec) {
-		ret = f_fb->cmd_exec(f_fb, cmdbuf);
-		if (ret != FASTBOOT_CMD_FALLTHROUGH)
-			goto done;
-	}
-
-	fb_run_command(f_fb, cmdbuf, cmd_dispatch_info,
-				ARRAY_SIZE(cmd_dispatch_info));
-done:
+	fastboot_exec_cmd(&f_fb->fastboot, cmdbuf);
 	*cmdbuf = '\0';
 	req->actual = 0;
 	memset(req->buf, 0, EP_BUFFER_SIZE);
 	usb_ep_queue(ep, req);
 }
-
-static int fastboot_globalvars_init(void)
-{
-	if (IS_ENABLED(CONFIG_USB_GADGET_FASTBOOT_SPARSE))
-		globalvar_add_simple_int("usbgadget.fastboot_max_download_size",
-				 &fastboot_max_download_size, "%u");
-
-	return 0;
-}
-
-device_initcall(fastboot_globalvars_init);
-
-BAREBOX_MAGICVAR_NAMED(global_usbgadget_fastboot_max_download_size,
-		       global.usbgadget.fastboot_max_download_size,
-		       "Fastboot maximum download size");
diff --git a/drivers/usb/gadget/multi.c b/drivers/usb/gadget/multi.c
index d6edfb8cf..445cc3242 100644
--- a/drivers/usb/gadget/multi.c
+++ b/drivers/usb/gadget/multi.c
@@ -127,10 +127,7 @@ static int multi_bind_fastboot(struct usb_composite_dev *cdev)
 	}
 
 	opts = container_of(fi_fastboot, struct f_fastboot_opts, func_inst);
-	opts->files = gadget_multi_opts->fastboot_opts.files;
-	opts->cmd_exec = gadget_multi_opts->fastboot_opts.cmd_exec;
-	opts->cmd_flash = gadget_multi_opts->fastboot_opts.cmd_flash;
-	opts->export_bbu = gadget_multi_opts->fastboot_opts.export_bbu;
+	opts->common = gadget_multi_opts->fastboot_opts;
 
 	f_fastboot = usb_get_function(fi_fastboot);
 	if (IS_ERR(f_fastboot)) {
diff --git a/include/fastboot.h b/include/fastboot.h
new file mode 100644
index 000000000..3b6cae8a5
--- /dev/null
+++ b/include/fastboot.h
@@ -0,0 +1,66 @@
+#ifndef __FASTBOOT__
+#define __FASTBOOT__
+
+#include <common.h>
+#include <file-list.h>
+#include <net.h>
+
+/*
+ * Return codes for the exec_cmd callback above:
+ *
+ * FASTBOOT_CMD_FALLTHROUGH - Not handled by the external command dispatcher,
+ *                            handle it with internal dispatcher
+ * Other than these negative error codes mean errors handling the command and
+ * zero means the command has been successfully handled.
+ */
+#define FASTBOOT_CMD_FALLTHROUGH	1
+
+struct fastboot {
+	int (*write)(struct fastboot *fb, const char *buf, unsigned int n);
+	void (*start_download)(struct fastboot *fb);
+
+	struct file_list *files;
+	int (*cmd_exec)(struct fastboot *fb, const char *cmd);
+	int (*cmd_flash)(struct fastboot *fb, struct file_list_entry *entry,
+			 const char *filename, const void *buf, size_t len);
+	int download_fd;
+	void *buf;
+
+	bool active;
+
+	size_t download_bytes;
+	size_t download_size;
+	struct list_head variables;
+};
+
+/**
+ * struct fastboot_opts - options to configure fastboot
+ * @files:	A file_list containing the files (partitions) to export via fastboot
+ * @export_bbu:	Automatically include the partitions provided by barebox update (bbu)
+ */
+struct fastboot_opts {
+	struct file_list *files;
+	bool export_bbu;
+	int (*cmd_exec)(struct fastboot *fb, const char *cmd);
+	int (*cmd_flash)(struct fastboot *fb, struct file_list_entry *entry,
+			 const char *filename, const void *buf, size_t len);
+};
+
+enum fastboot_msg_type {
+	FASTBOOT_MSG_OKAY,
+	FASTBOOT_MSG_FAIL,
+	FASTBOOT_MSG_INFO,
+	FASTBOOT_MSG_DATA,
+};
+
+int fastboot_generic_init(struct fastboot *fb, bool export_bbu);
+void fastboot_generic_close(struct fastboot *fb);
+void fastboot_generic_free(struct fastboot *fb);
+int fastboot_handle_download_data(struct fastboot *fb, const void *buffer,
+				  unsigned int len);
+int fastboot_tx_print(struct fastboot *fb, enum fastboot_msg_type type,
+		      const char *fmt, ...);
+void fastboot_start_download_generic(struct fastboot *fb);
+void fastboot_download_finished(struct fastboot *fb);
+void fastboot_exec_cmd(struct fastboot *fb, const char *cmdbuf);
+#endif
diff --git a/include/usb/fastboot.h b/include/usb/fastboot.h
index c0775c67d..a3609ba5d 100644
--- a/include/usb/fastboot.h
+++ b/include/usb/fastboot.h
@@ -1,45 +1,17 @@
 #ifndef _USB_FASTBOOT_H
 #define _USB_FASTBOOT_H
 
-#include <linux/types.h>
-#include <file-list.h>
 #include <usb/composite.h>
-
-struct f_fastboot;
+#include <fastboot.h>
 
 /**
  * struct f_fastboot_opts - options to configure the fastboot gadget
+ * @common:	Options common to all fastboot protocol variants
  * @func_inst:	The USB function instance to register on
- * @files:	A file_list containing the files (partitions) to export via fastboot
- * @export_bbu:	Automatically include the partitions provided by barebox update (bbu)
  */
 struct f_fastboot_opts {
+	struct fastboot_opts common;
 	struct usb_function_instance func_inst;
-	struct file_list *files;
-	bool export_bbu;
-	int (*cmd_exec)(struct f_fastboot *, const char *cmd);
-	int (*cmd_flash)(struct f_fastboot *, struct file_list_entry *entry,
-			 const char *filename, const void *buf, size_t len);
-};
-
-/*
- * Return codes for the exec_cmd callback above:
- *
- * FASTBOOT_CMD_FALLTHROUGH - Not handled by the external command dispatcher,
- *                            handle it with internal dispatcher
- * Other than these negative error codes mean errors handling the command and
- * zero means the command has been successfully handled.
- */
-#define FASTBOOT_CMD_FALLTHROUGH	1
-
-enum fastboot_msg_type {
-	FASTBOOT_MSG_OKAY,
-	FASTBOOT_MSG_FAIL,
-	FASTBOOT_MSG_INFO,
-	FASTBOOT_MSG_DATA,
 };
 
-int fastboot_tx_print(struct f_fastboot *f_fb, enum fastboot_msg_type type,
-		      const char *fmt, ...);
-
 #endif /* _USB_FASTBOOT_H */
diff --git a/include/usb/gadget-multi.h b/include/usb/gadget-multi.h
index 030e604fe..9bb6c889f 100644
--- a/include/usb/gadget-multi.h
+++ b/include/usb/gadget-multi.h
@@ -6,7 +6,7 @@
 #include <usb/usbserial.h>
 
 struct f_multi_opts {
-	struct f_fastboot_opts fastboot_opts;
+	struct fastboot_opts fastboot_opts;
 	struct f_dfu_opts dfu_opts;
 	int create_acm;
 	void (*release)(struct f_multi_opts *opts);
-- 
2.17.1


_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH 2/3] fastboot net: implement fastboot over UDP
  2020-02-28 20:48 [PATCH 0/3] Support for fastboot over UDP Daniel Glöckner
  2020-02-28 20:48 ` [PATCH 1/3] fastboot: split generic code from USB gadget Daniel Glöckner
@ 2020-02-28 20:48 ` Daniel Glöckner
  2020-03-05  7:50   ` Sascha Hauer
  2020-02-28 20:48 ` [PATCH 3/3] fastboot net: workaround for receiving before sending Daniel Glöckner
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 12+ messages in thread
From: Daniel Glöckner @ 2020-02-28 20:48 UTC (permalink / raw)
  To: barebox; +Cc: Edmund Henniges, Daniel Glöckner

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>
---
 common/fastboot.c      |   3 +
 include/fastboot.h     |   1 +
 include/fastboot_net.h |  12 ++
 net/Kconfig            |  17 ++
 net/Makefile           |   1 +
 net/fastboot.c         | 437 +++++++++++++++++++++++++++++++++++++++++
 6 files changed, 471 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 58a095efa..4a11ac54b 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 3b6cae8a5..503d21bc7 100644
--- a/include/fastboot.h
+++ b/include/fastboot.h
@@ -51,6 +51,7 @@ enum fastboot_msg_type {
 	FASTBOOT_MSG_FAIL,
 	FASTBOOT_MSG_INFO,
 	FASTBOOT_MSG_DATA,
+	FASTBOOT_MSG_NONE,
 };
 
 int fastboot_generic_init(struct fastboot *fb, bool export_bbu);
diff --git a/include/fastboot_net.h b/include/fastboot_net.h
new file mode 100644
index 000000000..e4b9d9809
--- /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 12b6bdb56..aaf621559 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -31,4 +31,21 @@ 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.
+
+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 nv.fastboot.files.
 endif
diff --git a/net/Makefile b/net/Makefile
index eb8d43915..962b2dec5 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 000000000..41d1859ab
--- /dev/null
+++ b/net/fastboot.c
@@ -0,0 +1,437 @@
+// 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,
+};
+
+struct __packed fastboot_header {
+	uint8_t id;
+	uint8_t flags;
+	uint16_t seq;
+};
+
+struct fastboot_net {
+	struct fastboot fastboot;
+
+	struct net_connection *net_con;
+	struct fastboot_header response_header;
+	struct poller_struct poller;
+	bool active_download;
+	bool reinit;
+	bool may_print;
+	char command[65];
+
+	IPaddr_t host_addr;
+	uint16_t host_port;
+	uint8_t host_mac[ETH_ALEN];
+	uint16_t sequence_number;
+	uint16_t 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->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_print) {
+		net_poll();
+		if (fbn->reinit)
+			return 0;
+	}
+
+	response_header = fbn->response_header;
+	response_header.flags = 0;
+	response_header.seq = htons(fbn->sequence_number);
+	++fbn->sequence_number;
+	fbn->may_print = 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);
+
+	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->may_print = true;
+	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);
+	uint16_t 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_handle_type_fastboot(fbn, header,
+						      fastboot_data, len);
+		else
+			fastboot_check_retransmit(fbn, header);
+		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);
+
+	net_poll();
+	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;
+
+	if (fbn->active_download) {
+		while (fbn->active_download)
+			net_poll();
+		if (!fbn->reinit)
+			fastboot_download_finished(&fbn->fastboot);
+	}
+}
+
+void fastboot_net_free(struct fastboot_net *fbn)
+{
+	fastboot_generic_close(&fbn->fastboot);
+	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 {
+		const char *fastboot_files = getenv("nv.fastboot.files");
+
+		fbn->fastboot.files = file_list_parse(fastboot_files);
+		ret = fastboot_generic_init(&fbn->fastboot, false);
+	}
+	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);
+
+	fbn->poller.func = fastboot_poll;
+	ret = poller_register(&fbn->poller);
+	if (ret)
+		goto fail_poller;
+
+	return fbn;
+
+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.17.1


_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH 3/3] fastboot net: workaround for receiving before sending
  2020-02-28 20:48 [PATCH 0/3] Support for fastboot over UDP Daniel Glöckner
  2020-02-28 20:48 ` [PATCH 1/3] fastboot: split generic code from USB gadget Daniel Glöckner
  2020-02-28 20:48 ` [PATCH 2/3] fastboot net: implement fastboot over UDP Daniel Glöckner
@ 2020-02-28 20:48 ` Daniel Glöckner
  2020-03-05  7:54 ` [PATCH 0/3] Support for fastboot over UDP Sascha Hauer
  2020-03-09  7:14 ` Sascha Hauer
  4 siblings, 0 replies; 12+ messages in thread
From: Daniel Glöckner @ 2020-02-28 20:48 UTC (permalink / raw)
  To: barebox; +Cc: Daniel Glöckner

eth_rx will poll a device only if it is active, but the only way to
activate a device is by calling eth_check_open. Although eth_check_open is
called both by eth_send and __eth_rx, only the call from eth_send has an
effect, because eth_rx will not call __eth_rx unless it is active.

Signed-off-by: Daniel Glöckner <dg@emlix.com>
---
 include/net.h  | 1 +
 net/eth.c      | 2 +-
 net/fastboot.c | 1 +
 3 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/include/net.h b/include/net.h
index 6912a557b..b9b821c9a 100644
--- a/include/net.h
+++ b/include/net.h
@@ -83,6 +83,7 @@ int eth_set_ethaddr(struct eth_device *edev, const char *ethaddr);
 
 int eth_send(struct eth_device *edev, void *packet, int length);	   /* Send a packet		*/
 int eth_rx(void);			/* Check for received packets	*/
+int eth_check_open(struct eth_device *edev);
 
 /* associate a MAC address to a ethernet device. Should be called by
  * board code for boards which store their MAC address at some unusual
diff --git a/net/eth.c b/net/eth.c
index 53d24baa1..25ed9a88e 100644
--- a/net/eth.c
+++ b/net/eth.c
@@ -215,7 +215,7 @@ static int eth_carrier_check(struct eth_device *edev, int force)
  * Check if we have a current ethernet device and
  * eventually open it if we have to.
  */
-static int eth_check_open(struct eth_device *edev)
+int eth_check_open(struct eth_device *edev)
 {
 	int ret;
 
diff --git a/net/fastboot.c b/net/fastboot.c
index 41d1859ab..57128258d 100644
--- a/net/fastboot.c
+++ b/net/fastboot.c
@@ -393,6 +393,7 @@ struct fastboot_net *fastboot_net_init(struct fastboot_opts *opts)
 	}
 	net_udp_bind(fbn->net_con, FASTBOOT_PORT);
 
+	eth_check_open(fbn->net_con->edev);
 	fbn->poller.func = fastboot_poll;
 	ret = poller_register(&fbn->poller);
 	if (ret)
-- 
2.17.1


_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/3] fastboot: split generic code from USB gadget
  2020-02-28 20:48 ` [PATCH 1/3] fastboot: split generic code from USB gadget Daniel Glöckner
@ 2020-03-05  7:25   ` Sascha Hauer
  0 siblings, 0 replies; 12+ messages in thread
From: Sascha Hauer @ 2020-03-05  7:25 UTC (permalink / raw)
  To: Daniel Glöckner; +Cc: barebox, Edmund Henniges

On Fri, Feb 28, 2020 at 09:48:21PM +0100, Daniel Glöckner wrote:
> From: Edmund Henniges <eh@emlix.com>
> 
> The fastboot specification describes other protocols beyond USB. Allow
> these to reuse the generic parts of the existing fastboot code when they
> are implemented.
> 
> Most of the changes in common/fastboot.c are due to the renaming of struct
> f_fastboot *f_fb to struct fastboot *fb.
> 
> Signed-off-by: Edmund Henniges <eh@emlix.com>
> Signed-off-by: Daniel Glöckner <dg@emlix.com>
> ---
>  common/Kconfig                  |  35 ++
>  common/Makefile                 |   1 +
>  common/fastboot.c               | 955 +++++++++++++++++++++++++++++++
>  drivers/usb/gadget/Kconfig      |   3 +-
>  drivers/usb/gadget/f_fastboot.c | 968 ++------------------------------
>  drivers/usb/gadget/multi.c      |   5 +-
>  include/fastboot.h              |  66 +++
>  include/usb/fastboot.h          |  34 +-
>  include/usb/gadget-multi.h      |   2 +-
>  9 files changed, 1105 insertions(+), 964 deletions(-)
>  create mode 100644 common/fastboot.c
>  create mode 100644 include/fastboot.h
> 
> diff --git a/common/Kconfig b/common/Kconfig
> index 02ef3631e..bed7e49db 100644
> --- a/common/Kconfig
> +++ b/common/Kconfig
> @@ -114,6 +114,9 @@ config USBGADGET_START
>  config BOOT
>  	bool
>  
> +config FASTBOOT_BASE
> +	bool
> +
>  menu "General Settings"
>  
>  config LOCALVERSION
> @@ -1013,6 +1016,38 @@ config PBL_OPTEE
>  
>  endmenu
>  
> +config FASTBOOT_SPARSE
> +	bool
> +	depends on FASTBOOT_BASE
> +	select IMAGE_SPARSE
> +	prompt "Enable Fastboot sparse image support"
> +	help
> +	  Sparse images are a way for the fastboot protocol to write
> +	  images that are bigger than the available memory. If unsure,
> +	  say yes here.
> +
> +config FASTBOOT_BUF
> +	bool
> +	depends on FASTBOOT_BASE
> +	prompt "Download files to temporary buffer instead of file"
> +	help
> +	  With this option enabled the fastboot code will download files to a
> +	  temporary buffer instead of a temporary file. Normally you want to
> +	  use a file as this also works when your memory is fragmented. However,
> +	  in some special cases, when the file consumer also better copes with
> +	  a buffer, then using a buffer might be better.
> +
> +	  Say no here unless you know what you are doing.
> +
> +config FASTBOOT_CMD_OEM
> +	bool
> +	depends on FASTBOOT_BASE
> +	prompt "Enable OEM commands"
> +	help
> +	  This option enables the fastboot "oem" group of commands. They allow to
> +	  executing arbitrary barebox commands and may be disabled in secure
> +	  environments.

I think these entries deserve an extra menu. Looking at the menu we now
have entries like "Download files to temporary buffer instead of file"
without giving any context that this is a fastboot option.

Does it make sense to add the fastboot USB and fastboot net options to
that menu as well? That way all fastboot options would be together and
not split into three different areas in the menu hierarchy.

Sascha

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 2/3] fastboot net: implement fastboot over UDP
  2020-02-28 20:48 ` [PATCH 2/3] fastboot net: implement fastboot over UDP Daniel Glöckner
@ 2020-03-05  7:50   ` Sascha Hauer
  2020-03-05 20:15     ` Daniel Glöckner
  0 siblings, 1 reply; 12+ messages in thread
From: Sascha Hauer @ 2020-03-05  7:50 UTC (permalink / raw)
  To: Daniel Glöckner; +Cc: barebox, Edmund Henniges

On Fri, Feb 28, 2020 at 09:48:22PM +0100, Daniel Glöckner wrote:
> 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>
> ---
>  common/fastboot.c      |   3 +
>  include/fastboot.h     |   1 +
>  include/fastboot_net.h |  12 ++
>  net/Kconfig            |  17 ++
>  net/Makefile           |   1 +
>  net/fastboot.c         | 437 +++++++++++++++++++++++++++++++++++++++++
>  6 files changed, 471 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 58a095efa..4a11ac54b 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 3b6cae8a5..503d21bc7 100644
> --- a/include/fastboot.h
> +++ b/include/fastboot.h
> @@ -51,6 +51,7 @@ enum fastboot_msg_type {
>  	FASTBOOT_MSG_FAIL,
>  	FASTBOOT_MSG_INFO,
>  	FASTBOOT_MSG_DATA,
> +	FASTBOOT_MSG_NONE,
>  };
>  
>  int fastboot_generic_init(struct fastboot *fb, bool export_bbu);
> diff --git a/include/fastboot_net.h b/include/fastboot_net.h
> new file mode 100644
> index 000000000..e4b9d9809
> --- /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 12b6bdb56..aaf621559 100644
> --- a/net/Kconfig
> +++ b/net/Kconfig
> @@ -31,4 +31,21 @@ 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.
> +
> +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 nv.fastboot.files.
>  endif
> diff --git a/net/Makefile b/net/Makefile
> index eb8d43915..962b2dec5 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 000000000..41d1859ab
> --- /dev/null
> +++ b/net/fastboot.c
> @@ -0,0 +1,437 @@
> +// 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,
> +};
> +
> +struct __packed fastboot_header {
> +	uint8_t id;
> +	uint8_t flags;
> +	uint16_t seq;
> +};
> +
> +struct fastboot_net {
> +	struct fastboot fastboot;
> +
> +	struct net_connection *net_con;
> +	struct fastboot_header response_header;
> +	struct poller_struct poller;
> +	bool active_download;
> +	bool reinit;
> +	bool may_print;
> +	char command[65];
> +
> +	IPaddr_t host_addr;
> +	uint16_t host_port;
> +	uint8_t host_mac[ETH_ALEN];
> +	uint16_t sequence_number;
> +	uint16_t 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->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_print) {
> +		net_poll();
> +		if (fbn->reinit)
> +			return 0;
> +	}
> +
> +	response_header = fbn->response_header;
> +	response_header.flags = 0;
> +	response_header.seq = htons(fbn->sequence_number);
> +	++fbn->sequence_number;
> +	fbn->may_print = 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);
> +
> +	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->may_print = true;
> +	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);
> +	uint16_t 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_handle_type_fastboot(fbn, header,
> +						      fastboot_data, len);
> +		else
> +			fastboot_check_retransmit(fbn, header);
> +		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);
> +
> +	net_poll();
> +	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;
> +
> +	if (fbn->active_download) {
> +		while (fbn->active_download)
> +			net_poll();
> +		if (!fbn->reinit)
> +			fastboot_download_finished(&fbn->fastboot);
> +	}
> +}
> +
> +void fastboot_net_free(struct fastboot_net *fbn)
> +{
> +	fastboot_generic_close(&fbn->fastboot);
> +	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 {
> +		const char *fastboot_files = getenv("nv.fastboot.files");

Please don't use nv variables directly, always use the corresponding
global variable.

For USB fastboot we have a variable global.usbgadget.fastboot_function
for the same purpose. We should probably use the same variable for both
fastboot variants and maybe also for DFU. How about moving the variable
name out of that usbgadget namespace and use something like
global.update_partitions? There might be better names...

> +
> +		fbn->fastboot.files = file_list_parse(fastboot_files);
> +		ret = fastboot_generic_init(&fbn->fastboot, false);
> +	}
> +	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);
> +
> +	fbn->poller.func = fastboot_poll;
> +	ret = poller_register(&fbn->poller);
> +	if (ret)
> +		goto fail_poller;

With this you call net_poll() from inside a poller. I just tested the
next obvious step and created a poller which calls net_poll() and
removed all the other calls to net_poll(). This however brought up a
problem. The fec_imx driver (in my case, other drivers do the same) uses
is_timeout() in its mdio read function. Now it can happen that some code
reads from the mdio bus which in turn triggers a poller run which then
checks for a link and ends up in the same mdio read function we already
come from.

Interestingly this only became a problem with my change, I tested your
fastboot net patches as-is and it works fluently. I have the gut feeling
though that the same problem should exist in principle without my change
too. We should sort that out first before merging this series. My
favourite solution would be to move net_poll() inside a poller indeed,
but I currently have no good idea how to fix this mdio read problem.

Sascha

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 0/3] Support for fastboot over UDP
  2020-02-28 20:48 [PATCH 0/3] Support for fastboot over UDP Daniel Glöckner
                   ` (2 preceding siblings ...)
  2020-02-28 20:48 ` [PATCH 3/3] fastboot net: workaround for receiving before sending Daniel Glöckner
@ 2020-03-05  7:54 ` Sascha Hauer
  2020-03-09  7:14 ` Sascha Hauer
  4 siblings, 0 replies; 12+ messages in thread
From: Sascha Hauer @ 2020-03-05  7:54 UTC (permalink / raw)
  To: Daniel Glöckner; +Cc: barebox

Hi Daniel,

Nice stuff this is ;)

I gave it a try and it works well so far. We seem to have a problem
though with calling net_poll() from a poller function, see my mail to 2/3.

What I currently do not like is the limitation to only bring up UDP
fastboot during startup, it should be possible to do that during runtime
at least optionally.

Regards,
  Sascha

On Fri, Feb 28, 2020 at 09:48:20PM +0100, Daniel Glöckner wrote:
> This patch series adds support the UDP variant of the fastboot protocol
> that was already supported in its USB form by Barebox. There is also a
> TCP variant, but Barebox obviously can't support that one with its
> current network stack.
> 
> The first patch separates all functions that can be reused into a file in
> common/. Although git didn't pick up the similarities, common/fastboot.c
> can be easily diffed against the old drivers/usb/gadget/f_fastboot.c,
> especially after replacing struct f_fb with fb and struct f_fastboot with
> struct fastboot.
> 
> The global.usbgadget.fastboot_max_download_size variable has not been
> renamed because someone might expect it to have that name. But I'll
> happily drop the usbgadget part from the name if you prefer.
> 
> The generic Kconfig options have been renamed. We could add options
> with the old name that select the new options to make the transition
> easier.
> 
> The second patch contains the implementation. The code was initially
> taken from U-Boot, but had to be rewritten heavily to fit the way the
> existing Barebox fastboot code executes commands and prints messages.
> Contrary to U-Boot it does not send keep alive messages every 30
> seconds because everything is executed from a poller. The timeout of
> Google's fastboot tool is 60 seconds.
> 
> The third patch was necessary to allow network interfaces to be activated
> without sending packets. The fastboot code sends packets only in reply to
> received packets.
> 
> Daniel Glöckner (1):
>   fastboot net: workaround for receiving before sending
> 
> Edmund Henniges (2):
>   fastboot: split generic code from USB gadget
>   fastboot net: implement fastboot over UDP
> 
>  common/Kconfig                  |  35 ++
>  common/Makefile                 |   1 +
>  common/fastboot.c               | 958 +++++++++++++++++++++++++++++++
>  drivers/usb/gadget/Kconfig      |   3 +-
>  drivers/usb/gadget/f_fastboot.c | 968 ++------------------------------
>  drivers/usb/gadget/multi.c      |   5 +-
>  include/fastboot.h              |  67 +++
>  include/fastboot_net.h          |  12 +
>  include/net.h                   |   1 +
>  include/usb/fastboot.h          |  34 +-
>  include/usb/gadget-multi.h      |   2 +-
>  net/Kconfig                     |  17 +
>  net/Makefile                    |   1 +
>  net/eth.c                       |   2 +-
>  net/fastboot.c                  | 438 +++++++++++++++
>  15 files changed, 1579 insertions(+), 965 deletions(-)
>  create mode 100644 common/fastboot.c
>  create mode 100644 include/fastboot.h
>  create mode 100644 include/fastboot_net.h
>  create mode 100644 net/fastboot.c
> 
> -- 
> 2.17.1
> 
> 
> _______________________________________________
> barebox mailing list
> barebox@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/barebox

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 2/3] fastboot net: implement fastboot over UDP
  2020-03-05  7:50   ` Sascha Hauer
@ 2020-03-05 20:15     ` Daniel Glöckner
  2020-03-06 19:36       ` Sascha Hauer
  0 siblings, 1 reply; 12+ messages in thread
From: Daniel Glöckner @ 2020-03-05 20:15 UTC (permalink / raw)
  To: Sascha Hauer; +Cc: barebox, Edmund Henniges

Am 03/05/20 um 08:50 schrieb Sascha Hauer:
>> +		const char *fastboot_files = getenv("nv.fastboot.files");
> 
> Please don't use nv variables directly, always use the corresponding
> global variable.
> 
> For USB fastboot we have a variable global.usbgadget.fastboot_function
> for the same purpose. We should probably use the same variable for both
> fastboot variants and maybe also for DFU. How about moving the variable
> name out of that usbgadget namespace and use something like
> global.update_partitions? There might be better names...

Since there is also the question what to do with global.usbgadget
.fastboot_max_download_size, we could introduce a new namespace for
fastboot and call the variables global.fastboot.update_partitions
and global.fastboot.max_download_size. Although I would prefer
"partitions" to "update_partitions".

> Interestingly this only became a problem with my change, I tested your
> fastboot net patches as-is and it works fluently.

Maybe it works with the fastboot code because the poller is registered
in a very late initcall. Or did it cause problems for you after all
initcalls had run?

> My
> favourite solution would be to move net_poll() inside a poller indeed,
> but I currently have no good idea how to fix this mdio read problem.

How about wrapping the body of mdiobus_read, mdiobus_write, eth_send,
eth_rx, eth_check_open, and maybe even mdiobus_register, eth_register,
and eth_unregister in

net_entered++;
...
net_entered--;

and then immediately returning from net_poll if net_entered != 0 on
entry? That's similar to how poller_call prevents reentrance.


Now that I have shifted the focus to poller_call, how about changing that
function to allow being entered N times (with N configurable since stack
space is limited) to allow fastboot to send keep alive messages and the
watchdog to be serviced? We could add a flag to poller_struct that tells
poller_call to skip this poller when entered again when its function is
already running. The keep alives would then be sent from another poller.

Hm, I think I'll rewrite fastboot_poll to return while waiting for more
download data. Then only the command execution can trigger a watchdog
reset.

Best regards,

  Daniel

-- 
Dipl.-Math. Daniel Glöckner, emlix GmbH, http://www.emlix.com
Fon +49 551 30664-0, Fax +49 551 30664-11,
Gothaer Platz 3, 37083 Göttingen, Germany
Sitz der Gesellschaft: Göttingen, Amtsgericht Göttingen HR B 3160
Geschäftsführung: Heike Jordan, Dr. Uwe Kracke
Ust-IdNr.: DE 205 198 055

emlix - your embedded linux partner

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 2/3] fastboot net: implement fastboot over UDP
  2020-03-05 20:15     ` Daniel Glöckner
@ 2020-03-06 19:36       ` Sascha Hauer
  0 siblings, 0 replies; 12+ messages in thread
From: Sascha Hauer @ 2020-03-06 19:36 UTC (permalink / raw)
  To: Daniel Glöckner; +Cc: barebox, Edmund Henniges

On Thu, Mar 05, 2020 at 09:15:57PM +0100, Daniel Glöckner wrote:
> Am 03/05/20 um 08:50 schrieb Sascha Hauer:
> >> +		const char *fastboot_files = getenv("nv.fastboot.files");
> > 
> > Please don't use nv variables directly, always use the corresponding
> > global variable.
> > 
> > For USB fastboot we have a variable global.usbgadget.fastboot_function
> > for the same purpose. We should probably use the same variable for both
> > fastboot variants and maybe also for DFU. How about moving the variable
> > name out of that usbgadget namespace and use something like
> > global.update_partitions? There might be better names...
> 
> Since there is also the question what to do with global.usbgadget
> .fastboot_max_download_size, we could introduce a new namespace for
> fastboot and call the variables global.fastboot.update_partitions
> and global.fastboot.max_download_size. Although I would prefer
> "partitions" to "update_partitions".
> 
> > Interestingly this only became a problem with my change, I tested your
> > fastboot net patches as-is and it works fluently.
> 
> Maybe it works with the fastboot code because the poller is registered
> in a very late initcall. Or did it cause problems for you after all
> initcalls had run?
> 
> > My
> > favourite solution would be to move net_poll() inside a poller indeed,
> > but I currently have no good idea how to fix this mdio read problem.
> 
> How about wrapping the body of mdiobus_read, mdiobus_write, eth_send,
> eth_rx, eth_check_open, and maybe even mdiobus_register, eth_register,
> and eth_unregister in
> 
> net_entered++;
> ...
> net_entered--;
> 
> and then immediately returning from net_poll if net_entered != 0 on
> entry? That's similar to how poller_call prevents reentrance.

Yes, something like that. I created a series which implements such a
scheme, but a little more generic so that hopefully it will help with
the same problem in other areas as well.

Regards
 Sascha

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 0/3] Support for fastboot over UDP
  2020-02-28 20:48 [PATCH 0/3] Support for fastboot over UDP Daniel Glöckner
                   ` (3 preceding siblings ...)
  2020-03-05  7:54 ` [PATCH 0/3] Support for fastboot over UDP Sascha Hauer
@ 2020-03-09  7:14 ` Sascha Hauer
  2020-03-09 15:38   ` Daniel Glöckner
  4 siblings, 1 reply; 12+ messages in thread
From: Sascha Hauer @ 2020-03-09  7:14 UTC (permalink / raw)
  To: Daniel Glöckner; +Cc: barebox

On Fri, Feb 28, 2020 at 09:48:20PM +0100, Daniel Glöckner wrote:
> This patch series adds support the UDP variant of the fastboot protocol
> that was already supported in its USB form by Barebox. There is also a
> TCP variant, but Barebox obviously can't support that one with its
> current network stack.
> 
> The first patch separates all functions that can be reused into a file in
> common/. Although git didn't pick up the similarities, common/fastboot.c
> can be easily diffed against the old drivers/usb/gadget/f_fastboot.c,
> especially after replacing struct f_fb with fb and struct f_fastboot with
> struct fastboot.
> 
> The global.usbgadget.fastboot_max_download_size variable has not been
> renamed because someone might expect it to have that name. But I'll
> happily drop the usbgadget part from the name if you prefer.
> 
> The generic Kconfig options have been renamed. We could add options
> with the old name that select the new options to make the transition
> easier.

We can rename the options in the defconfig files by hand. Only a handful
of them have fastboot enabled.

> 
> The second patch contains the implementation. The code was initially
> taken from U-Boot, but had to be rewritten heavily to fit the way the
> existing Barebox fastboot code executes commands and prints messages.
> Contrary to U-Boot it does not send keep alive messages every 30
> seconds because everything is executed from a poller.

How does that prevent you from sending keep palive messages? Sending
packages from a poller should work.

> 
> The third patch was necessary to allow network interfaces to be activated
> without sending packets. The fastboot code sends packets only in reply to
> received packets.

I'll have a look at this topic the next round. I don't like this
implicit ethopen-during-send we currently do anyway and I think this can
be approved.

Sascha

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 0/3] Support for fastboot over UDP
  2020-03-09  7:14 ` Sascha Hauer
@ 2020-03-09 15:38   ` Daniel Glöckner
  2020-03-12  8:26     ` Sascha Hauer
  0 siblings, 1 reply; 12+ messages in thread
From: Daniel Glöckner @ 2020-03-09 15:38 UTC (permalink / raw)
  To: Sascha Hauer; +Cc: barebox

Am 03/09/20 um 08:14 schrieb Sascha Hauer:
> On Fri, Feb 28, 2020 at 09:48:20PM +0100, Daniel Glöckner wrote:
>> Contrary to U-Boot it does not send keep alive messages every 30
>> seconds because everything is executed from a poller.
> 
> How does that prevent you from sending keep palive messages? Sending
> packages from a poller should work.

When long running functions like cb_erase and cb_flash are executed in a
poller, we can't currently use another poller to check if it is time to
send a keep alive message.

If we add a slice to poller_struct that is checked and then acquired in
poller_call, it would be possible. But as I wrote, we might then want to
limit the number of pollers executed "in parallel" because of the
limited stack space.

Having a slice in poller_struct also allows pollers to list the
resources necessary to run them.

Best regards

  Daniel

-- 
Dipl.-Math. Daniel Glöckner, emlix GmbH, http://www.emlix.com
Fon +49 551 30664-0, Fax +49 551 30664-11,
Gothaer Platz 3, 37083 Göttingen, Germany
Sitz der Gesellschaft: Göttingen, Amtsgericht Göttingen HR B 3160
Geschäftsführung: Heike Jordan, Dr. Uwe Kracke
Ust-IdNr.: DE 205 198 055

emlix - your embedded linux partner

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 0/3] Support for fastboot over UDP
  2020-03-09 15:38   ` Daniel Glöckner
@ 2020-03-12  8:26     ` Sascha Hauer
  0 siblings, 0 replies; 12+ messages in thread
From: Sascha Hauer @ 2020-03-12  8:26 UTC (permalink / raw)
  To: Daniel Glöckner; +Cc: barebox

On Mon, Mar 09, 2020 at 04:38:05PM +0100, Daniel Glöckner wrote:
> Am 03/09/20 um 08:14 schrieb Sascha Hauer:
> > On Fri, Feb 28, 2020 at 09:48:20PM +0100, Daniel Glöckner wrote:
> >> Contrary to U-Boot it does not send keep alive messages every 30
> >> seconds because everything is executed from a poller.
> > 
> > How does that prevent you from sending keep palive messages? Sending
> > packages from a poller should work.
> 
> When long running functions like cb_erase and cb_flash are executed in a
> poller, we can't currently use another poller to check if it is time to
> send a keep alive message.
> 
> If we add a slice to poller_struct that is checked and then acquired in
> poller_call, it would be possible. But as I wrote, we might then want to
> limit the number of pollers executed "in parallel" because of the
> limited stack space.

That's a good idea. I think the stack space won't be a problem as the
number of pollers running in parallel is limited by the number of
existing pollers.

I have updated the series to do exactly that.

Sascha

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2020-03-12  8:26 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-02-28 20:48 [PATCH 0/3] Support for fastboot over UDP Daniel Glöckner
2020-02-28 20:48 ` [PATCH 1/3] fastboot: split generic code from USB gadget Daniel Glöckner
2020-03-05  7:25   ` Sascha Hauer
2020-02-28 20:48 ` [PATCH 2/3] fastboot net: implement fastboot over UDP Daniel Glöckner
2020-03-05  7:50   ` Sascha Hauer
2020-03-05 20:15     ` Daniel Glöckner
2020-03-06 19:36       ` Sascha Hauer
2020-02-28 20:48 ` [PATCH 3/3] fastboot net: workaround for receiving before sending Daniel Glöckner
2020-03-05  7:54 ` [PATCH 0/3] Support for fastboot over UDP Sascha Hauer
2020-03-09  7:14 ` Sascha Hauer
2020-03-09 15:38   ` Daniel Glöckner
2020-03-12  8:26     ` Sascha Hauer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox