// SPDX-License-Identifier: GPL-2.0-or-later /* * (C) Copyright 2008 - 2009 * Windriver, * Tom Rix * * Copyright 2011 Sebastian Andrzej Siewior * * Copyright 2014 Linaro, Ltd. * Rob Herring * * Copyright 2014 Sascha Hauer * Ported to barebox * * Copyright 2020 Edmund Henniges * Copyright 2020 Daniel Glöckner * Split off of generic parts */ #define pr_fmt(fmt) "fastboot: " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FASTBOOT_VERSION "0.4" static unsigned int fastboot_max_download_size = SZ_8M; static int fastboot_bbu; static char *fastboot_partitions; struct fb_variable { char *name; char *value; struct list_head list; }; 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; INIT_LIST_HEAD(&fb->variables); 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); } fb->tempname = make_temp("fastboot"); if (!fb->tempname) return -ENOMEM; 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); } free(fb->tempname); 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", [FASTBOOT_MSG_NONE] = "", }; 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 = ≈ 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_NONE: 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) { fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, ""); 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; 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); fb->download_fd = 0; printf("\n"); fastboot_tx_print(fb, FASTBOOT_MSG_INFO, "Downloading %d bytes finished", fb->download_bytes); fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, ""); } void fastboot_abort(struct fastboot *fb) { if (fb->download_fd > 0) { close(fb->download_fd); fb->download_fd = 0; } fb->active = false; unlink(fb->tempname); } 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 (fb->download_fd > 0) { pr_err("%s called and %s is still opened\n", __func__, fb->tempname); close(fb->download_fd); } fb->download_fd = open(fb->tempname, 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("bootm.image", ""); data.os_file = fb->tempname; 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, size_t len) { struct ubiformat_args args = { .yes = 1, .image = file, .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(fb->tempname); 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) { size_t 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, 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; enum filetype filetype; filetype = file_name_detect_type(fb->tempname); 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, fb->tempname, 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_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, fb->tempname, 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)) { void *buf; 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..."); ret = read_file_2(fb->tempname, &data.len, &buf, fb->download_size); if (ret) { fastboot_tx_print(fb, FASTBOOT_MSG_FAIL, "reading barebox"); goto out; } data.image = buf; data.imagefile = fb->tempname; ret = barebox_update(&data, handler); if (ret) fastboot_tx_print(fb, FASTBOOT_MSG_FAIL, "update barebox: %s", strerror(-ret)); free(buf); goto out; } copy: ret = copy_file(fb->tempname, 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, ""); unlink(fb->tempname); } 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 = parse_assignment(var); if (!value) { ret = -EINVAL; goto out; } 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)); } bool get_fastboot_bbu(void) { return fastboot_bbu; } const char *get_fastboot_partitions(void) { return fastboot_partitions; } static int fastboot_globalvars_init(void) { if (IS_ENABLED(CONFIG_FASTBOOT_SPARSE)) globalvar_add_simple_int("fastboot.max_download_size", &fastboot_max_download_size, "%u"); globalvar_add_simple_bool("fastboot.bbu", &fastboot_bbu); globalvar_add_simple_string("fastboot.partitions", &fastboot_partitions); globalvar_alias_deprecated("usbgadget.fastboot_function", "fastboot.partitions"); globalvar_alias_deprecated("usbgadget.fastboot_bbu", "fastboot.bbu"); globalvar_alias_deprecated("usbgadget.fastboot_max_download_size", "fastboot.max_download_size"); return 0; } device_initcall(fastboot_globalvars_init); BAREBOX_MAGICVAR(global.fastboot.max_download_size, "Fastboot maximum download size"); BAREBOX_MAGICVAR(global.fastboot.partitions, "Partitions exported for update via fastboot"); BAREBOX_MAGICVAR(global.fastboot.bbu, "Export barebox update handlers via fastboot");