diff --git a/cmd/Kconfig b/cmd/Kconfig index 30cf63f..17ca0bb 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -655,8 +655,8 @@ config CMD_FASTBOOT This enables the command "fastboot" which enables the Android fastboot mode for the platform. Fastboot is a protocol for downloading images, flashing and device control used on - Android devices. Fastboot requires support for acting as a USB - device. + Android devices. Fastboot requires either the network stack + enabled or support for acting as a USB device. See doc/README.android-fastboot for more information. diff --git a/cmd/fastboot.c b/cmd/fastboot.c index a5ec5f4..557257a 100644 --- a/cmd/fastboot.c +++ b/cmd/fastboot.c @@ -10,10 +10,32 @@ #include #include #include +#include +#include #include -static int do_fastboot(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) +static int do_fastboot_udp(int argc, char *const argv[], + uintptr_t buf_addr, size_t buf_size) { +#if CONFIG_IS_ENABLED(UDP_FUNCTION_FASTBOOT) + int err = net_loop(FASTBOOT); + + if (err < 0) { + printf("fastboot udp error: %d\n", err); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +#else + pr_err("Fastboot UDP not enabled\n"); + return CMD_RET_FAILURE; +#endif +} + +static int do_fastboot_usb(int argc, char *const argv[], + uintptr_t buf_addr, size_t buf_size) +{ +#if CONFIG_IS_ENABLED(USB_FUNCTION_FASTBOOT) int controller_index; char *usb_controller; int ret; @@ -58,11 +80,70 @@ exit: board_usb_cleanup(controller_index, USB_INIT_DEVICE); return ret; +#else + pr_err("Fastboot USB not enabled\n"); + return CMD_RET_FAILURE; +#endif +} + +static int do_fastboot(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) +{ + uintptr_t buf_addr = (uintptr_t)NULL; + size_t buf_size = 0; + + if (argc < 2) + return CMD_RET_USAGE; + + while (argc > 1 && **(argv + 1) == '-') { + char *arg = *++argv; + + --argc; + while (*++arg) { + switch (*arg) { + case 'l': + if (--argc <= 0) + return CMD_RET_USAGE; + buf_addr = simple_strtoul(*++argv, NULL, 16); + goto NXTARG; + + case 's': + if (--argc <= 0) + return CMD_RET_USAGE; + buf_size = simple_strtoul(*++argv, NULL, 16); + goto NXTARG; + + default: + return CMD_RET_USAGE; + } + } +NXTARG: + ; + } + + fastboot_init((void *)buf_addr, buf_size); + + if (!strcmp(argv[1], "udp")) + return do_fastboot_udp(argc, argv, buf_addr, buf_size); + + if (!strcmp(argv[1], "usb")) { + argv++; + argc--; + } + + return do_fastboot_usb(argc, argv, buf_addr, buf_size); } +#ifdef CONFIG_SYS_LONGHELP +static char fastboot_help_text[] = + "[-l addr] [-s size] usb | udp\n" + "\taddr - address of buffer used during data transfers (" + __stringify(CONFIG_FASTBOOT_BUF_ADDR) ")\n" + "\tsize - size of buffer used during data transfers (" + __stringify(CONFIG_FASTBOOT_BUF_SIZE) ")" + ; +#endif + U_BOOT_CMD( - fastboot, 2, 1, do_fastboot, - "use USB Fastboot protocol", - "\n" - " - run as a fastboot usb device" + fastboot, CONFIG_SYS_MAXARGS, 1, do_fastboot, + "run as a fastboot usb or udp device", fastboot_help_text ); diff --git a/drivers/fastboot/Kconfig b/drivers/fastboot/Kconfig index 0c9ced5..625f016 100644 --- a/drivers/fastboot/Kconfig +++ b/drivers/fastboot/Kconfig @@ -14,6 +14,13 @@ config USB_FUNCTION_FASTBOOT help This enables the USB part of the fastboot gadget. +config UDP_FUNCTION_FASTBOOT + depends on NET + select FASTBOOT + bool "Enable fastboot protocol over UDP" + help + This enables the fastboot protocol over UDP. + if FASTBOOT config FASTBOOT_BUF_ADDR diff --git a/drivers/fastboot/Makefile b/drivers/fastboot/Makefile index e4bd389..8831096 100644 --- a/drivers/fastboot/Makefile +++ b/drivers/fastboot/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0+ obj-y += fb_common.o - +obj-$(CONFIG_UDP_FUNCTION_FASTBOOT) += fb_getvar.o +obj-$(CONFIG_UDP_FUNCTION_FASTBOOT) += fb_command.o obj-$(CONFIG_FASTBOOT_FLASH_MMC) += fb_mmc.o obj-$(CONFIG_FASTBOOT_FLASH_NAND) += fb_nand.o diff --git a/drivers/fastboot/fb_command.c b/drivers/fastboot/fb_command.c new file mode 100644 index 0000000..af4f500 --- /dev/null +++ b/drivers/fastboot/fb_command.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (C) 2016 The Android Open Source Project + */ + +#include +#include +#include +#include +#include +#include +#include + +/** + * image_size - final fastboot image size + */ +static u32 image_size; + +/** + * fastboot_bytes_received - number of bytes received in the current download + */ +static u32 fastboot_bytes_received; + +/** + * fastboot_bytes_expected - number of bytes expected in the current download + */ +static u32 fastboot_bytes_expected; + +static void okay(char *, char *); +static void getvar(char *, char *); +static void download(char *, char *); +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH) +static void flash(char *, char *); +static void erase(char *, char *); +#endif +static void reboot_bootloader(char *, char *); + +static const struct { + const char *command; + void (*dispatch)(char *cmd_parameter, char *response); +} commands[FASTBOOT_COMMAND_COUNT] = { + [FASTBOOT_COMMAND_GETVAR] = { + .command = "getvar", + .dispatch = getvar + }, + [FASTBOOT_COMMAND_DOWNLOAD] = { + .command = "download", + .dispatch = download + }, +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH) + [FASTBOOT_COMMAND_FLASH] = { + .command = "flash", + .dispatch = flash + }, + [FASTBOOT_COMMAND_ERASE] = { + .command = "erase", + .dispatch = erase + }, +#endif + [FASTBOOT_COMMAND_BOOT] = { + .command = "boot", + .dispatch = okay + }, + [FASTBOOT_COMMAND_CONTINUE] = { + .command = "continue", + .dispatch = okay + }, + [FASTBOOT_COMMAND_REBOOT] = { + .command = "reboot", + .dispatch = okay + }, + [FASTBOOT_COMMAND_REBOOT_BOOTLOADER] = { + .command = "reboot-bootloader", + .dispatch = reboot_bootloader + }, + [FASTBOOT_COMMAND_SET_ACTIVE] = { + .command = "set_active", + .dispatch = okay + }, +}; + +/** + * fastboot_handle_command - Handle fastboot command + * + * @cmd_string: Pointer to command string + * @response: Pointer to fastboot response buffer + * + * Return: Executed command, or -1 if not recognized + */ +int fastboot_handle_command(char *cmd_string, char *response) +{ + int i; + char *cmd_parameter; + + cmd_parameter = cmd_string; + strsep(&cmd_parameter, ":"); + + for (i = 0; i < FASTBOOT_COMMAND_COUNT; i++) { + if (!strcmp(commands[i].command, cmd_string)) { + if (commands[i].dispatch) { + commands[i].dispatch(cmd_parameter, + response); + return i; + } else { + break; + } + } + } + + pr_err("command %s not recognized.\n", cmd_string); + fastboot_fail("unrecognized command", response); + return -1; +} + +/** + * okay() - Send bare OKAY response + * + * @cmd_parameter: Pointer to command parameter + * @response: Pointer to fastboot response buffer + * + * Send a bare OKAY fastboot response. This is used where the command is + * valid, but all the work is done after the response has been sent (e.g. + * boot, reboot etc.) + */ +static void okay(char *cmd_parameter, char *response) +{ + fastboot_okay(NULL, response); +} + +/** + * getvar() - Read a config/version variable + * + * @cmd_parameter: Pointer to command parameter + * @response: Pointer to fastboot response buffer + */ +static void getvar(char *cmd_parameter, char *response) +{ + fastboot_getvar(cmd_parameter, response); +} + +/** + * fastboot_download() - Start a download transfer from the client + * + * @cmd_parameter: Pointer to command parameter + * @response: Pointer to fastboot response buffer + */ +static void download(char *cmd_parameter, char *response) +{ + char *tmp; + + if (!cmd_parameter) { + fastboot_fail("Expected command parameter", response); + return; + } + fastboot_bytes_received = 0; + fastboot_bytes_expected = simple_strtoul(cmd_parameter, &tmp, 16); + if (fastboot_bytes_expected == 0) { + fastboot_fail("Expected nonzero image size", response); + return; + } + /* + * Nothing to download yet. Response is of the form: + * [DATA|FAIL]$cmd_parameter + * + * where cmd_parameter is an 8 digit hexadecimal number + */ + if (fastboot_bytes_expected > fastboot_buf_size) { + fastboot_fail(cmd_parameter, response); + } else { + printf("Starting download of %d bytes\n", + fastboot_bytes_expected); + fastboot_response("DATA", response, "%s", cmd_parameter); + } +} + +/** + * fastboot_data_remaining() - return bytes remaining in current transfer + * + * Return: Number of bytes left in the current download + */ +u32 fastboot_data_remaining(void) +{ + return fastboot_bytes_expected - fastboot_bytes_received; +} + +/** + * fastboot_data_download() - Copy image data to fastboot_buf_addr. + * + * @fastboot_data: Pointer to received fastboot data + * @fastboot_data_len: Length of received fastboot data + * @response: Pointer to fastboot response buffer + * + * Copies image data from fastboot_data to fastboot_buf_addr. Writes to + * response. fastboot_bytes_received is updated to indicate the number + * of bytes that have been transferred. + * + * On completion sets image_size and ${filesize} to the total size of the + * downloaded image. + */ +void fastboot_data_download(const void *fastboot_data, + unsigned int fastboot_data_len, + char *response) +{ +#define BYTES_PER_DOT 0x20000 + u32 pre_dot_num, now_dot_num; + + if (fastboot_data_len == 0 || + (fastboot_bytes_received + fastboot_data_len) > + fastboot_bytes_expected) { + fastboot_fail("Received invalid data length", + response); + return; + } + /* Download data to fastboot_buf_addr */ + memcpy(fastboot_buf_addr + fastboot_bytes_received, + fastboot_data, fastboot_data_len); + + pre_dot_num = fastboot_bytes_received / BYTES_PER_DOT; + fastboot_bytes_received += fastboot_data_len; + now_dot_num = fastboot_bytes_received / BYTES_PER_DOT; + + if (pre_dot_num != now_dot_num) { + putc('.'); + if (!(now_dot_num % 74)) + putc('\n'); + } + *response = '\0'; +} + +/** + * fastboot_data_complete() - Mark current transfer complete + * + * @response: Pointer to fastboot response buffer + * + * Set image_size and ${filesize} to the total size of the downloaded image. + */ +void fastboot_data_complete(char *response) +{ + /* Download complete. Respond with "OKAY" */ + fastboot_okay(NULL, response); + printf("\ndownloading of %d bytes finished\n", fastboot_bytes_received); + image_size = fastboot_bytes_received; + env_set_hex("filesize", image_size); + fastboot_bytes_expected = 0; + fastboot_bytes_received = 0; +} + +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH) +/** + * flash() - write the downloaded image to the indicated partition. + * + * @cmd_parameter: Pointer to partition name + * @response: Pointer to fastboot response buffer + * + * Writes the previously downloaded image to the partition indicated by + * cmd_parameter. Writes to response. + */ +static void flash(char *cmd_parameter, char *response) +{ +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH_MMC) + fastboot_mmc_flash_write(cmd_parameter, fastboot_buf_addr, image_size, + response); +#endif +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH_NAND) + fastboot_nand_flash_write(cmd_parameter, fastboot_buf_addr, image_size, + response); +#endif +} + +/** + * erase() - erase the indicated partition. + * + * @cmd_parameter: Pointer to partition name + * @response: Pointer to fastboot response buffer + * + * Erases the partition indicated by cmd_parameter (clear to 0x00s). Writes + * to response. + */ +static void erase(char *cmd_parameter, char *response) +{ +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH_MMC) + fastboot_mmc_erase(cmd_parameter, response); +#endif +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH_NAND) + fastboot_nand_erase(cmd_parameter, response); +#endif +} +#endif + +/** + * reboot_bootloader() - Sets reboot bootloader flag. + * + * @cmd_parameter: Pointer to command parameter + * @response: Pointer to fastboot response buffer + */ +static void reboot_bootloader(char *cmd_parameter, char *response) +{ + if (fastboot_set_reboot_flag()) + fastboot_fail("Cannot set reboot flag", response); + else + fastboot_okay(NULL, response); +} diff --git a/drivers/fastboot/fb_common.c b/drivers/fastboot/fb_common.c index 79e080a..c6e06aa 100644 --- a/drivers/fastboot/fb_common.c +++ b/drivers/fastboot/fb_common.c @@ -12,6 +12,22 @@ #include #include +#include + +/** + * fastboot_buf_addr - base address of the fastboot download buffer + */ +void *fastboot_buf_addr; + +/** + * fastboot_buf_size - size of the fastboot download buffer + */ +u32 fastboot_buf_size; + +/** + * fastboot_progress_callback - callback executed during long operations + */ +void (*fastboot_progress_callback)(const char *msg); /** * fastboot_response() - Writes a response of the form "$tag$reason". @@ -74,3 +90,80 @@ int __weak fastboot_set_reboot_flag(void) { return -ENOSYS; } + +/** + * fastboot_get_progress_callback() - Return progress callback + * + * Return: Pointer to function called during long operations + */ +void (*fastboot_get_progress_callback(void))(const char *) +{ + return fastboot_progress_callback; +} + +/** + * fastboot_boot() - Execute fastboot boot command + * + * If ${fastboot_bootcmd} is set, run that command to execute the boot + * process, if that returns, then exit the fastboot server and return + * control to the caller. + * + * Otherwise execute "bootm ", if that fails, reset + * the board. + */ +void fastboot_boot(void) +{ + char *s; + + s = env_get("fastboot_bootcmd"); + if (s) { + run_command(s, CMD_FLAG_ENV); + } else { + static char boot_addr_start[12]; + static char *const bootm_args[] = { + "bootm", boot_addr_start, NULL + }; + + snprintf(boot_addr_start, sizeof(boot_addr_start) - 1, + "0x%p", fastboot_buf_addr); + printf("Booting kernel at %s...\n\n\n", boot_addr_start); + + do_bootm(NULL, 0, 2, bootm_args); + + /* + * This only happens if image is somehow faulty so we start + * over. We deliberately leave this policy to the invocation + * of fastbootcmd if that's what's being run + */ + do_reset(NULL, 0, 0, NULL); + } +} + +/** + * fastboot_set_progress_callback() - set progress callback + * + * @progress: Pointer to progress callback + * + * Set a callback which is invoked periodically during long running operations + * (flash and erase). This can be used (for example) by the UDP transport to + * send INFO responses to keep the client alive whilst those commands are + * executing. + */ +void fastboot_set_progress_callback(void (*progress)(const char *msg)) +{ + fastboot_progress_callback = progress; +} + +/* + * fastboot_init() - initialise new fastboot protocol session + * + * @buf_addr: Pointer to download buffer, or NULL for default + * @buf_size: Size of download buffer, or zero for default + */ +void fastboot_init(void *buf_addr, u32 buf_size) +{ + fastboot_buf_addr = buf_addr ? buf_addr : + (void *)CONFIG_FASTBOOT_BUF_ADDR; + fastboot_buf_size = buf_size ? buf_size : CONFIG_FASTBOOT_BUF_SIZE; + fastboot_set_progress_callback(NULL); +} diff --git a/drivers/fastboot/fb_getvar.c b/drivers/fastboot/fb_getvar.c new file mode 100644 index 0000000..4d264c9 --- /dev/null +++ b/drivers/fastboot/fb_getvar.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (C) 2016 The Android Open Source Project + */ + +#include +#include +#include +#include +#include +#include +#include + +static void getvar_version(char *var_parameter, char *response); +static void getvar_bootloader_version(char *var_parameter, char *response); +static void getvar_downloadsize(char *var_parameter, char *response); +static void getvar_serialno(char *var_parameter, char *response); +static void getvar_version_baseband(char *var_parameter, char *response); +static void getvar_product(char *var_parameter, char *response); +static void getvar_current_slot(char *var_parameter, char *response); +static void getvar_slot_suffixes(char *var_parameter, char *response); +static void getvar_has_slot(char *var_parameter, char *response); +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH_MMC) +static void getvar_partition_type(char *part_name, char *response); +#endif +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH) +static void getvar_partition_size(char *part_name, char *response); +#endif + +static const struct { + const char *variable; + void (*dispatch)(char *var_parameter, char *response); +} getvar_dispatch[] = { + { + .variable = "version", + .dispatch = getvar_version + }, { + .variable = "bootloader-version", + .dispatch = getvar_bootloader_version + }, { + .variable = "version-bootloader", + .dispatch = getvar_bootloader_version + }, { + .variable = "downloadsize", + .dispatch = getvar_downloadsize + }, { + .variable = "max-download-size", + .dispatch = getvar_downloadsize + }, { + .variable = "serialno", + .dispatch = getvar_serialno + }, { + .variable = "version-baseband", + .dispatch = getvar_version_baseband + }, { + .variable = "product", + .dispatch = getvar_product + }, { + .variable = "current-slot", + .dispatch = getvar_current_slot + }, { + .variable = "slot-suffixes", + .dispatch = getvar_slot_suffixes + }, { + .variable = "has_slot", + .dispatch = getvar_has_slot +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH_MMC) + }, { + .variable = "partition-type", + .dispatch = getvar_partition_type +#endif +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH) + }, { + .variable = "partition-size", + .dispatch = getvar_partition_size +#endif + } +}; + +static void getvar_version(char *var_parameter, char *response) +{ + fastboot_okay(FASTBOOT_VERSION, response); +} + +static void getvar_bootloader_version(char *var_parameter, char *response) +{ + fastboot_okay(U_BOOT_VERSION, response); +} + +static void getvar_downloadsize(char *var_parameter, char *response) +{ + fastboot_response("OKAY", response, "0x%08x", fastboot_buf_size); +} + +static void getvar_serialno(char *var_parameter, char *response) +{ + const char *tmp = env_get("serial#"); + + if (tmp) + fastboot_okay(tmp, response); + else + fastboot_fail("Value not set", response); +} + +static void getvar_version_baseband(char *var_parameter, char *response) +{ + fastboot_okay("N/A", response); +} + +static void getvar_product(char *var_parameter, char *response) +{ + const char *board = env_get("board"); + + if (board) + fastboot_okay(board, response); + else + fastboot_fail("Board not set", response); +} + +static void getvar_current_slot(char *var_parameter, char *response) +{ + /* A/B not implemented, for now always return _a */ + fastboot_okay("_a", response); +} + +static void getvar_slot_suffixes(char *var_parameter, char *response) +{ + fastboot_okay("_a,_b", response); +} + +static void getvar_has_slot(char *part_name, char *response) +{ + if (part_name && (!strcmp(part_name, "boot") || + !strcmp(part_name, "system"))) + fastboot_okay("yes", response); + else + fastboot_okay("no", response); +} + +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH_MMC) +static void getvar_partition_type(char *part_name, char *response) +{ + int r; + struct blk_desc *dev_desc; + disk_partition_t part_info; + + r = fastboot_mmc_get_part_info(part_name, &dev_desc, &part_info, + response); + if (r >= 0) { + r = fs_set_blk_dev_with_part(dev_desc, r); + if (r < 0) + fastboot_fail("failed to set partition", response); + else + fastboot_okay(fs_get_type_name(), response); + } +} +#endif + +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH) +static void getvar_partition_size(char *part_name, char *response) +{ + int r; + size_t size; + +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH_MMC) + struct blk_desc *dev_desc; + disk_partition_t part_info; + + r = fastboot_mmc_get_part_info(part_name, &dev_desc, &part_info, + response); + if (r >= 0) + size = part_info.size; +#endif +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH_NAND) + struct part_info *part_info; + + r = fastboot_nand_get_part_info(part_name, &part_info, response); + if (r >= 0) + size = part_info->size; +#endif + if (r >= 0) + fastboot_response("OKAY", response, "0x%016zx", size); +} +#endif + +/** + * fastboot_getvar() - Writes variable indicated by cmd_parameter to response. + * + * @cmd_parameter: Pointer to command parameter + * @response: Pointer to fastboot response buffer + * + * Look up cmd_parameter first as an environment variable of the form + * fastboot., if that exists return use its value to set + * response. + * + * Otherwise lookup the name of variable and execute the appropriate + * function to return the requested value. + */ +void fastboot_getvar(char *cmd_parameter, char *response) +{ + if (!cmd_parameter) { + fastboot_fail("missing var", response); + } else { +#define FASTBOOT_ENV_PREFIX "fastboot." + int i; + char *var_parameter = cmd_parameter; + char envstr[FASTBOOT_RESPONSE_LEN]; + const char *s; + + snprintf(envstr, sizeof(envstr) - 1, + FASTBOOT_ENV_PREFIX "%s", cmd_parameter); + s = env_get(envstr); + if (s) { + fastboot_response("OKAY", response, "%s", s); + return; + } + + strsep(&var_parameter, ":"); + for (i = 0; i < ARRAY_SIZE(getvar_dispatch); ++i) { + if (!strcmp(getvar_dispatch[i].variable, + cmd_parameter)) { + getvar_dispatch[i].dispatch(var_parameter, + response); + return; + } + } + pr_warn("WARNING: unknown variable: %s\n", cmd_parameter); + fastboot_fail("Variable not implemented", response); + } +} diff --git a/drivers/fastboot/fb_mmc.c b/drivers/fastboot/fb_mmc.c index 47fcf74..4c1c7fd 100644 --- a/drivers/fastboot/fb_mmc.c +++ b/drivers/fastboot/fb_mmc.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -15,6 +16,8 @@ #include #include +#define FASTBOOT_MAX_BLK_WRITE 16384 + #define BOOT_PARTITION_NAME "boot" struct fb_mmc_sparse { @@ -43,13 +46,48 @@ static int part_get_info_by_name_or_alias(struct blk_desc *dev_desc, return ret; } +/** + * fb_mmc_blk_write() - Write/erase MMC in chunks of FASTBOOT_MAX_BLK_WRITE + * + * @block_dev: Pointer to block device + * @start: First block to write/erase + * @blkcnt: Count of blocks + * @buffer: Pointer to data buffer for write or NULL for erase + */ +static lbaint_t fb_mmc_blk_write(struct blk_desc *block_dev, lbaint_t start, + lbaint_t blkcnt, const void *buffer) +{ + lbaint_t blk = start; + lbaint_t blks_written; + lbaint_t cur_blkcnt; + lbaint_t blks = 0; + int i; + + for (i = 0; i < blkcnt; i += FASTBOOT_MAX_BLK_WRITE) { + cur_blkcnt = min((int)blkcnt - i, FASTBOOT_MAX_BLK_WRITE); + if (buffer) { + if (fastboot_progress_callback) + fastboot_progress_callback("writing"); + blks_written = blk_dwrite(block_dev, blk, cur_blkcnt, + buffer + (i * block_dev->blksz)); + } else { + if (fastboot_progress_callback) + fastboot_progress_callback("erasing"); + blks_written = blk_derase(block_dev, blk, cur_blkcnt); + } + blk += blks_written; + blks += blks_written; + } + return blks; +} + static lbaint_t fb_mmc_sparse_write(struct sparse_storage *info, lbaint_t blk, lbaint_t blkcnt, const void *buffer) { struct fb_mmc_sparse *sparse = info->priv; struct blk_desc *dev_desc = sparse->dev_desc; - return blk_dwrite(dev_desc, blk, blkcnt, buffer); + return fb_mmc_blk_write(dev_desc, blk, blkcnt, buffer); } static lbaint_t fb_mmc_sparse_reserve(struct sparse_storage *info, @@ -60,7 +98,7 @@ static lbaint_t fb_mmc_sparse_reserve(struct sparse_storage *info, static void write_raw_image(struct blk_desc *dev_desc, disk_partition_t *info, const char *part_name, void *buffer, - unsigned int download_bytes, char *response) + u32 download_bytes, char *response) { lbaint_t blkcnt; lbaint_t blks; @@ -77,7 +115,8 @@ static void write_raw_image(struct blk_desc *dev_desc, disk_partition_t *info, puts("Flashing Raw Image\n"); - blks = blk_dwrite(dev_desc, info->start, blkcnt, buffer); + blks = fb_mmc_blk_write(dev_desc, info->start, blkcnt, buffer); + if (blks != blkcnt) { pr_err("failed writing to device %d\n", dev_desc->devnum); fastboot_fail("failed writing to device", response); @@ -148,7 +187,7 @@ static lbaint_t fb_mmc_get_boot_header(struct blk_desc *dev_desc, */ static int fb_mmc_update_zimage(struct blk_desc *dev_desc, void *download_buffer, - unsigned int download_bytes, + u32 download_bytes, char *response) { uintptr_t hdr_addr; /* boot image header address */ @@ -252,6 +291,38 @@ static int fb_mmc_update_zimage(struct blk_desc *dev_desc, #endif /** + * fastboot_mmc_get_part_info() - Lookup eMMC partion by name + * + * @part_name: Named partition to lookup + * @dev_desc: Pointer to returned blk_desc pointer + * @part_info: Pointer to returned disk_partition_t + * @response: Pointer to fastboot response buffer + */ +int fastboot_mmc_get_part_info(char *part_name, struct blk_desc **dev_desc, + disk_partition_t *part_info, char *response) +{ + int r; + + *dev_desc = blk_get_dev("mmc", CONFIG_FASTBOOT_FLASH_MMC_DEV); + if (!*dev_desc) { + fastboot_fail("block device not found", response); + return -ENOENT; + } + if (!part_name) { + fastboot_fail("partition not found", response); + return -ENOENT; + } + + r = part_get_info_by_name_or_alias(*dev_desc, part_name, part_info); + if (r < 0) { + fastboot_fail("partition not found", response); + return r; + } + + return r; +} + +/** * fastboot_mmc_flash_write() - Write image to eMMC for fastboot * * @cmd: Named partition to write image to @@ -260,7 +331,7 @@ static int fb_mmc_update_zimage(struct blk_desc *dev_desc, * @response: Pointer to fastboot response buffer */ void fastboot_mmc_flash_write(const char *cmd, void *download_buffer, - unsigned int download_bytes, char *response) + u32 download_bytes, char *response) { struct blk_desc *dev_desc; disk_partition_t info; @@ -403,7 +474,8 @@ void fastboot_mmc_erase(const char *cmd, char *response) printf("Erasing blocks " LBAFU " to " LBAFU " due to alignment\n", blks_start, blks_start + blks_size); - blks = blk_derase(dev_desc, blks_start, blks_size); + blks = fb_mmc_blk_write(dev_desc, blks_start, blks_size, NULL); + if (blks != blks_size) { pr_err("failed erasing from device %d\n", dev_desc->devnum); fastboot_fail("failed erasing from device", response); diff --git a/drivers/fastboot/fb_nand.c b/drivers/fastboot/fb_nand.c index 535f541..526bc12 100644 --- a/drivers/fastboot/fb_nand.c +++ b/drivers/fastboot/fb_nand.c @@ -88,7 +88,7 @@ static int _fb_nand_erase(struct mtd_info *mtd, struct part_info *part) } static int _fb_nand_write(struct mtd_info *mtd, struct part_info *part, - void *buffer, unsigned int offset, + void *buffer, u32 offset, size_t length, size_t *written) { int flags = WITH_WR_VERIFY; @@ -146,6 +146,21 @@ static lbaint_t fb_nand_sparse_reserve(struct sparse_storage *info, } /** + * fastboot_nand_get_part_info() - Lookup NAND partion by name + * + * @part_name: Named device to lookup + * @part_info: Pointer to returned part_info pointer + * @response: Pointer to fastboot response buffer + */ +int fastboot_nand_get_part_info(char *part_name, struct part_info **part_info, + char *response) +{ + struct mtd_info *mtd = NULL; + + return fb_nand_lookup(part_name, &mtd, part_info, response); +} + +/** * fastboot_nand_flash_write() - Write image to NAND for fastboot * * @cmd: Named device to write image to @@ -154,7 +169,7 @@ static lbaint_t fb_nand_sparse_reserve(struct sparse_storage *info, * @response: Pointer to fastboot response buffer */ void fastboot_nand_flash_write(const char *cmd, void *download_buffer, - unsigned int download_bytes, char *response) + u32 download_bytes, char *response) { struct part_info *part; struct mtd_info *mtd = NULL; diff --git a/include/fastboot-internal.h b/include/fastboot-internal.h new file mode 100644 index 0000000..bf2f2b3 --- /dev/null +++ b/include/fastboot-internal.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef _FASTBOOT_INTERNAL_H_ +#define _FASTBOOT_INTERNAL_H_ + +/** + * fastboot_buf_addr - base address of the fastboot download buffer + */ +extern void *fastboot_buf_addr; + +/** + * fastboot_buf_size - size of the fastboot download buffer + */ +extern u32 fastboot_buf_size; + +/** + * fastboot_progress_callback - callback executed during long operations + */ +extern void (*fastboot_progress_callback)(const char *msg); + +/** + * fastboot_getvar() - Writes variable indicated by cmd_parameter to response. + * + * @cmd_parameter: Pointer to command parameter + * @response: Pointer to fastboot response buffer + * + * Look up cmd_parameter first as an environment variable of the form + * fastboot., if that exists return use its value to set + * response. + * + * Otherwise lookup the name of variable and execute the appropriate + * function to return the requested value. + */ +void fastboot_getvar(char *cmd_parameter, char *response); + +#endif diff --git a/include/fastboot.h b/include/fastboot.h index bf3d9e2..9a3d5ba 100644 --- a/include/fastboot.h +++ b/include/fastboot.h @@ -15,9 +15,29 @@ #define FASTBOOT_VERSION "0.4" /* The 64 defined bytes plus \0 */ +#define FASTBOOT_COMMAND_LEN (64 + 1) #define FASTBOOT_RESPONSE_LEN (64 + 1) /** + * All known commands to fastboot + */ +enum { + FASTBOOT_COMMAND_GETVAR = 0, + FASTBOOT_COMMAND_DOWNLOAD, +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH) + FASTBOOT_COMMAND_FLASH, + FASTBOOT_COMMAND_ERASE, +#endif + FASTBOOT_COMMAND_BOOT, + FASTBOOT_COMMAND_CONTINUE, + FASTBOOT_COMMAND_REBOOT, + FASTBOOT_COMMAND_REBOOT_BOOTLOADER, + FASTBOOT_COMMAND_SET_ACTIVE, + + FASTBOOT_COMMAND_COUNT +}; + +/** * fastboot_response() - Writes a response of the form "$tag$reason". * * @tag: The first part of the response @@ -43,5 +63,89 @@ void fastboot_fail(const char *reason, char *response); * @response: Pointer to fastboot response buffer */ void fastboot_okay(const char *reason, char *response); + +/** + * fastboot_set_reboot_flag() - Set flag to indicate reboot-bootloader + * + * Set flag which indicates that we should reboot into the bootloader + * following the reboot that fastboot executes after this function. + * + * This function should be overridden in your board file with one + * which sets whatever flag your board specific Android bootloader flow + * requires in order to re-enter the bootloader. + */ int fastboot_set_reboot_flag(void); + +/** + * fastboot_set_progress_callback() - set progress callback + * + * @progress: Pointer to progress callback + * + * Set a callback which is invoked periodically during long running operations + * (flash and erase). This can be used (for example) by the UDP transport to + * send INFO responses to keep the client alive whilst those commands are + * executing. + */ +void fastboot_set_progress_callback(void (*progress)(const char *msg)); + +/* + * fastboot_init() - initialise new fastboot protocol session + * + * @buf_addr: Pointer to download buffer, or NULL for default + * @buf_size: Size of download buffer, or zero for default + */ +void fastboot_init(void *buf_addr, u32 buf_size); + +/** + * fastboot_boot() - Execute fastboot boot command + * + * If ${fastboot_bootcmd} is set, run that command to execute the boot + * process, if that returns, then exit the fastboot server and return + * control to the caller. + * + * Otherwise execute "bootm ", if that fails, reset + * the board. + */ +void fastboot_boot(void); + +/** + * fastboot_handle_command() - Handle fastboot command + * + * @cmd_string: Pointer to command string + * @response: Pointer to fastboot response buffer + * + * Return: Executed command, or -1 if not recognized + */ +int fastboot_handle_command(char *cmd_string, char *response); + +/** + * fastboot_data_remaining() - return bytes remaining in current transfer + * + * Return: Number of bytes left in the current download + */ +u32 fastboot_data_remaining(void); + +/** + * fastboot_data_download() - Copy image data to fastboot_buf_addr. + * + * @fastboot_data: Pointer to received fastboot data + * @fastboot_data_len: Length of received fastboot data + * @response: Pointer to fastboot response buffer + * + * Copies image data from fastboot_data to fastboot_buf_addr. Writes to + * response. fastboot_bytes_received is updated to indicate the number + * of bytes that have been transferred. + */ +void fastboot_data_download(const void *fastboot_data, + unsigned int fastboot_data_len, char *response); + +/** + * fastboot_data_complete() - Mark current transfer complete + * + * @response: Pointer to fastboot response buffer + * + * Set image_size and ${filesize} to the total size of the downloaded image. + */ +void fastboot_data_complete(char *response); + #endif /* _FASTBOOT_H_ */ diff --git a/include/fb_mmc.h b/include/fb_mmc.h index daca81a..fd5db9e 100644 --- a/include/fb_mmc.h +++ b/include/fb_mmc.h @@ -3,6 +3,20 @@ * Copyright 2014 Broadcom Corporation. */ +#ifndef _FB_MMC_H_ +#define _FB_MMC_H_ + +/** + * fastboot_mmc_get_part_info() - Lookup eMMC partion by name + * + * @part_name: Named partition to lookup + * @dev_desc: Pointer to returned blk_desc pointer + * @part_info: Pointer to returned disk_partition_t + * @response: Pointer to fastboot response buffer + */ +int fastboot_mmc_get_part_info(char *part_name, struct blk_desc **dev_desc, + disk_partition_t *part_info, char *response); + /** * fastboot_mmc_flash_write() - Write image to eMMC for fastboot * @@ -12,8 +26,7 @@ * @response: Pointer to fastboot response buffer */ void fastboot_mmc_flash_write(const char *cmd, void *download_buffer, - unsigned int download_bytes, char *response); - + u32 download_bytes, char *response); /** * fastboot_mmc_flash_erase() - Erase eMMC for fastboot * @@ -21,3 +34,4 @@ void fastboot_mmc_flash_write(const char *cmd, void *download_buffer, * @response: Pointer to fastboot response buffer */ void fastboot_mmc_erase(const char *cmd, char *response); +#endif diff --git a/include/fb_nand.h b/include/fb_nand.h index b5d4c0c..08ab0e2 100644 --- a/include/fb_nand.h +++ b/include/fb_nand.h @@ -4,6 +4,21 @@ * Copyright 2015 Free Electrons. */ +#ifndef _FB_NAND_H_ +#define _FB_NAND_H_ + +#include + +/** + * fastboot_nand_get_part_info() - Lookup NAND partion by name + * + * @part_name: Named device to lookup + * @part_info: Pointer to returned part_info pointer + * @response: Pointer to fastboot response buffer + */ +int fastboot_nand_get_part_info(char *part_name, struct part_info **part_info, + char *response); + /** * fastboot_nand_flash_write() - Write image to NAND for fastboot * @@ -13,7 +28,8 @@ * @response: Pointer to fastboot response buffer */ void fastboot_nand_flash_write(const char *cmd, void *download_buffer, - unsigned int download_bytes, char *response); + u32 download_bytes, char *response); + /** * fastboot_nand_flash_erase() - Erase NAND for fastboot * @@ -21,3 +37,4 @@ void fastboot_nand_flash_write(const char *cmd, void *download_buffer, * @response: Pointer to fastboot response buffer */ void fastboot_nand_erase(const char *cmd, char *response); +#endif diff --git a/include/net.h b/include/net.h index 65f51d7..5760685 100644 --- a/include/net.h +++ b/include/net.h @@ -535,7 +535,7 @@ extern int net_restart_wrap; /* Tried all network devices */ enum proto_t { BOOTP, RARP, ARP, TFTPGET, DHCP, PING, DNS, NFS, CDP, NETCONS, SNTP, - TFTPSRV, TFTPPUT, LINKLOCAL + TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT }; extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/include/net/fastboot.h b/include/net/fastboot.h new file mode 100644 index 0000000..6860209 --- /dev/null +++ b/include/net/fastboot.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (C) 2016 The Android Open Source Project + */ + +#ifndef __NET_FASTBOOT_H__ +#define __NET_FASTBOOT_H__ + +/**********************************************************************/ +/* + * Global functions and variables. + */ + +/** + * Wait for incoming fastboot comands. + */ +void fastboot_start_server(void); + +/**********************************************************************/ + +#endif /* __NET_FASTBOOT_H__ */ diff --git a/net/Makefile b/net/Makefile index d1e8e01..0746687 100644 --- a/net/Makefile +++ b/net/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_CMD_PING) += ping.o obj-$(CONFIG_CMD_RARP) += rarp.o obj-$(CONFIG_CMD_SNTP) += sntp.o obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o +obj-$(CONFIG_UDP_FUNCTION_FASTBOOT) += fastboot.o # Disable this warning as it is triggered by: # sprintf(buf, index ? "foo%d" : "foo", index) diff --git a/net/fastboot.c b/net/fastboot.c new file mode 100644 index 0000000..a9f7c07 --- /dev/null +++ b/net/fastboot.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (C) 2016 The Android Open Source Project + */ + +#include +#include +#include +#include + +/* Fastboot port # defined in spec */ +#define WELL_KNOWN_PORT 5554 + +enum { + FASTBOOT_ERROR = 0, + FASTBOOT_QUERY = 1, + FASTBOOT_INIT = 2, + FASTBOOT_FASTBOOT = 3, +}; + +struct __packed fastboot_header { + uchar id; + uchar flags; + unsigned short seq; +}; + +#define PACKET_SIZE 1024 +#define DATA_SIZE (PACKET_SIZE - sizeof(struct fastboot_header)) + +/* Sequence number sent for every packet */ +static unsigned short sequence_number = 1; +static const unsigned short packet_size = PACKET_SIZE; +static const unsigned short udp_version = 1; + +/* Keep track of last packet for resubmission */ +static uchar last_packet[PACKET_SIZE]; +static unsigned int last_packet_len; + +static struct in_addr fastboot_remote_ip; +/* The UDP port at their end */ +static int fastboot_remote_port; +/* The UDP port at our end */ +static int fastboot_our_port; + +static void boot_downloaded_image(void); + +#if CONFIG_IS_ENABLED(FASTBOOT_FLASH) +/** + * fastboot_udp_send_info() - Send an INFO packet during long commands. + * + * @msg: String describing the reason for waiting + */ +static void fastboot_udp_send_info(const char *msg) +{ + uchar *packet; + uchar *packet_base; + int len = 0; + char response[FASTBOOT_RESPONSE_LEN] = {0}; + + struct fastboot_header response_header = { + .id = FASTBOOT_FASTBOOT, + .flags = 0, + .seq = htons(sequence_number) + }; + ++sequence_number; + packet = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE; + packet_base = packet; + + /* Write headers */ + memcpy(packet, &response_header, sizeof(response_header)); + packet += sizeof(response_header); + /* Write response */ + fastboot_response("INFO", response, "%s", msg); + memcpy(packet, response, strlen(response)); + packet += strlen(response); + + len = packet - packet_base; + + /* Save packet for retransmitting */ + last_packet_len = len; + memcpy(last_packet, packet_base, last_packet_len); + + net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip, + fastboot_remote_port, fastboot_our_port, len); +} + +/** + * fastboot_timed_send_info() - Send INFO packet every 30 seconds + * + * @msg: String describing the reason for waiting + * + * Send an INFO packet during long commands based on timer. An INFO packet + * is sent if the time is 30 seconds after start. Else, noop. + */ +static void fastboot_timed_send_info(const char *msg) +{ + static ulong start; + + /* Initialize timer */ + if (start == 0) + start = get_timer(0); + ulong time = get_timer(start); + /* Send INFO packet to host every 30 seconds */ + if (time >= 30000) { + start = get_timer(0); + fastboot_udp_send_info(msg); + } +} +#endif + +/** + * fastboot_send() - Sends a packet in response to received fastboot packet + * + * @header: Header for response packet + * @fastboot_data: Pointer to received fastboot data + * @fastboot_data_len: Length of received fastboot data + * @retransmit: Nonzero if sending last sent packet + */ +static void fastboot_send(struct fastboot_header header, char *fastboot_data, + unsigned int fastboot_data_len, uchar retransmit) +{ + uchar *packet; + uchar *packet_base; + int len = 0; + const char *error_msg = "An error occurred."; + short tmp; + struct fastboot_header response_header = header; + static char command[FASTBOOT_COMMAND_LEN]; + static int cmd = -1; + static bool pending_command; + char response[FASTBOOT_RESPONSE_LEN] = {0}; + + /* + * We will always be sending some sort of packet, so + * cobble together the packet headers now. + */ + packet = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE; + packet_base = packet; + + /* Resend last packet */ + if (retransmit) { + memcpy(packet, last_packet, last_packet_len); + net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip, + fastboot_remote_port, fastboot_our_port, + last_packet_len); + return; + } + + response_header.seq = htons(response_header.seq); + memcpy(packet, &response_header, sizeof(response_header)); + packet += sizeof(response_header); + + switch (header.id) { + case FASTBOOT_QUERY: + tmp = htons(sequence_number); + memcpy(packet, &tmp, sizeof(tmp)); + packet += sizeof(tmp); + break; + case FASTBOOT_INIT: + 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: + memcpy(packet, error_msg, strlen(error_msg)); + packet += strlen(error_msg); + break; + case FASTBOOT_FASTBOOT: + if (cmd == FASTBOOT_COMMAND_DOWNLOAD) { + if (!fastboot_data_len && !fastboot_data_remaining()) { + fastboot_data_complete(response); + } else { + fastboot_data_download(fastboot_data, + fastboot_data_len, + response); + } + } else if (!pending_command) { + strlcpy(command, fastboot_data, + min((size_t)fastboot_data_len + 1, + sizeof(command))); + pending_command = true; + } else { + cmd = fastboot_handle_command(command, response); + pending_command = false; + } + /* + * Sent some INFO packets, need to update sequence number in + * header + */ + if (header.seq != sequence_number) { + response_header.seq = htons(sequence_number); + memcpy(packet_base, &response_header, + sizeof(response_header)); + } + /* Write response to packet */ + memcpy(packet, response, strlen(response)); + packet += strlen(response); + break; + default: + pr_err("ID %d not implemented.\n", header.id); + return; + } + + len = packet - packet_base; + + /* Save packet for retransmitting */ + last_packet_len = len; + memcpy(last_packet, packet_base, last_packet_len); + + net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip, + fastboot_remote_port, fastboot_our_port, len); + + /* Continue boot process after sending response */ + if (!strncmp("OKAY", response, 4)) { + switch (cmd) { + case FASTBOOT_COMMAND_BOOT: + boot_downloaded_image(); + break; + + case FASTBOOT_COMMAND_CONTINUE: + net_set_state(NETLOOP_SUCCESS); + break; + + case FASTBOOT_COMMAND_REBOOT: + case FASTBOOT_COMMAND_REBOOT_BOOTLOADER: + do_reset(NULL, 0, 0, NULL); + break; + } + } + + if (!strncmp("OKAY", response, 4) || !strncmp("FAIL", response, 4)) + cmd = -1; +} + +/** + * boot_downloaded_image() - Boots into downloaded image. + */ +static void boot_downloaded_image(void) +{ + fastboot_boot(); + net_set_state(NETLOOP_SUCCESS); +} + +/** + * fastboot_handler() - Incoming UDP packet handler. + * + * @packet: Pointer to incoming UDP packet + * @dport: Destination UDP port + * @sip: Source IP address + * @sport: Source UDP port + * @len: Packet length + */ +static void fastboot_handler(uchar *packet, unsigned int dport, + struct in_addr sip, unsigned int sport, + unsigned int len) +{ + struct fastboot_header header; + char fastboot_data[DATA_SIZE] = {0}; + unsigned int fastboot_data_len = 0; + + if (dport != fastboot_our_port) + return; + + fastboot_remote_ip = sip; + fastboot_remote_port = sport; + + if (len < sizeof(struct fastboot_header) || len > PACKET_SIZE) + return; + memcpy(&header, packet, sizeof(header)); + header.flags = 0; + header.seq = ntohs(header.seq); + packet += sizeof(header); + len -= sizeof(header); + + switch (header.id) { + case FASTBOOT_QUERY: + fastboot_send(header, fastboot_data, 0, 0); + break; + case FASTBOOT_INIT: + case FASTBOOT_FASTBOOT: + fastboot_data_len = len; + if (len > 0) + memcpy(fastboot_data, packet, len); + if (header.seq == sequence_number) { + fastboot_send(header, fastboot_data, + fastboot_data_len, 0); + sequence_number++; + } else if (header.seq == sequence_number - 1) { + /* Retransmit last sent packet */ + fastboot_send(header, fastboot_data, + fastboot_data_len, 1); + } + break; + default: + pr_err("ID %d not implemented.\n", header.id); + header.id = FASTBOOT_ERROR; + fastboot_send(header, fastboot_data, 0, 0); + break; + } +} + +void fastboot_start_server(void) +{ + printf("Using %s device\n", eth_get_name()); + printf("Listening for fastboot command on %pI4\n", &net_ip); + + fastboot_our_port = WELL_KNOWN_PORT; + + fastboot_set_progress_callback(fastboot_timed_send_info); + net_set_udp_handler(fastboot_handler); + + /* zero out server ether in case the server ip has changed */ + memset(net_server_ethaddr, 0, 6); +} diff --git a/net/net.c b/net/net.c index 7f85211..a4932f4 100644 --- a/net/net.c +++ b/net/net.c @@ -87,6 +87,7 @@ #include #include #include +#include #include #if defined(CONFIG_LED_STATUS) #include @@ -451,6 +452,11 @@ restart: tftp_start_server(); break; #endif +#ifdef CONFIG_UDP_FUNCTION_FASTBOOT + case FASTBOOT: + fastboot_start_server(); + break; +#endif #if defined(CONFIG_CMD_DHCP) case DHCP: bootp_reset(); @@ -1322,6 +1328,7 @@ common: /* Fall through */ case NETCONS: + case FASTBOOT: case TFTPSRV: if (net_ip.s_addr == 0) { puts("*** ERROR: `ipaddr' not set\n");