summaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
authorEdmund Henniges <eh@emlix.com>2020-05-14 20:21:54 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2020-05-20 15:43:59 +0200
commit2879373370eb3685cd6622f5d800e9f72ec81df1 (patch)
treed57b9103755336b550b7e8397dfeb586e728ef02 /common
parent754f2e0d106d44dc89fdb4edbb54641d9525e62c (diff)
downloadbarebox-2879373370eb3685cd6622f5d800e9f72ec81df1.tar.gz
barebox-2879373370eb3685cd6622f5d800e9f72ec81df1.tar.xz
fastboot: split generic code from USB gadget
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> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'common')
-rw-r--r--common/Kconfig40
-rw-r--r--common/Makefile1
-rw-r--r--common/fastboot.c957
3 files changed, 998 insertions, 0 deletions
diff --git a/common/Kconfig b/common/Kconfig
index 460ac487cb..18796c6888 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -113,6 +113,9 @@ config USBGADGET_START
config BOOT
bool
+config FASTBOOT_BASE
+ bool
+
menu "General Settings"
config LOCALVERSION
@@ -1009,6 +1012,43 @@ config PBL_OPTEE
endmenu
+if FASTBOOT_BASE
+
+menu "Android Fastboot"
+
+config FASTBOOT_SPARSE
+ bool
+ 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
+ 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
+ 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
+
+endif
+
endmenu
menu "Debugging"
diff --git a/common/Makefile b/common/Makefile
index c14af692f9..53859d8d14 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 0000000000..d58f68f1bb
--- /dev/null
+++ b/common/fastboot.c
@@ -0,0 +1,957 @@
+/*
+ * (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 {
+ discard_range(fd, retlen, pos);
+
+ 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");