| /**************************************************************************** |
| * apps/system/fastboot/fastboot.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| #include <nuttx/memoryregion.h> |
| #include <nuttx/mtd/mtd.h> |
| #include <nuttx/version.h> |
| |
| #include <endian.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <syslog.h> |
| #include <unistd.h> |
| |
| #include <netinet/in.h> |
| #include <sys/boardctl.h> |
| #include <sys/epoll.h> |
| #include <sys/ioctl.h> |
| #include <sys/param.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/statfs.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define FASTBOOT_USBDEV "/dev/fastboot" |
| #define FASTBOOT_BLKDEV "/dev/%s" |
| |
| #define FASTBOOT_EP_BULKIN_IDX 1 |
| #define FASTBOOT_EP_BULKOUT_IDX 2 |
| #define FASTBOOT_EP_RETRY_TIMES 100 |
| #define FASTBOOT_EP_RETRY_DELAY_MS 10 |
| |
| #define FASTBOOT_MSG_LEN 64 |
| |
| #define FASTBOOT_SPARSE_MAGIC 0xed26ff3a |
| #define FASTBOOT_CHUNK_RAW 0xcac1 |
| #define FASTBOOT_CHUNK_FILL 0xcac2 |
| #define FASTBOOT_CHUNK_DONT_CARE 0xcac3 |
| #define FASTBOOT_CHUNK_CRC32 0xcac4 |
| |
| #define FASTBOOT_SPARSE_HEADER sizeof(struct fastboot_sparse_header_s) |
| #define FASTBOOT_CHUNK_HEADER sizeof(struct fastboot_chunk_header_s) |
| |
| /* Fastboot TCP Protocol v1 |
| * |
| * handshake: chars "FB" followed by a 2-digit base-10 ASCII version number |
| * data_size: 8-byte big-endian binary value before fastboot packet |
| * |
| * https://android.googlesource.com/platform/system/core/+/refs/heads/main\ |
| * /fastboot/README.md#tcp-protocol-v1 |
| */ |
| |
| #define FASTBOOT_TCP_HANDSHAKE "FB01" |
| #define FASTBOOT_TCP_HANDSHAKE_LEN 4 |
| #define FASTBOOT_TCP_PORT 5554 |
| |
| #define fb_info(...) syslog(LOG_INFO, ##__VA_ARGS__); |
| #define fb_err(...) syslog(LOG_ERR, ##__VA_ARGS__); |
| |
| /**************************************************************************** |
| * Private types |
| ****************************************************************************/ |
| |
| struct fastboot_ctx_s; |
| |
| struct fastboot_var_s |
| { |
| FAR struct fastboot_var_s *next; |
| FAR const char *name; |
| FAR const char *string; |
| int data; |
| }; |
| |
| struct fastboot_sparse_header_s |
| { |
| uint32_t magic; /* 0xed26ff3a */ |
| uint16_t major_version; /* (0x1) - reject images with higher major versions */ |
| uint16_t minor_version; /* (0x0) - allow images with higher minor versions */ |
| uint16_t file_hdr_sz; /* 28 bytes for first revision of the file format */ |
| uint16_t chunk_hdr_sz; /* 12 bytes for first revision of the file format */ |
| uint32_t blk_sz; /* block size in bytes, must be a multiple of 4 (4096) */ |
| uint32_t total_blks; /* total blocks in the non-sparse output image */ |
| uint32_t total_chunks; /* total chunks in the sparse input image */ |
| uint32_t image_checksum; /* CRC32 checksum of the original data, counting "don't care" */ |
| /* as 0. Standard 802.3 polynomial, use a Public Domain */ |
| /* table implementation */ |
| }; |
| |
| struct fastboot_chunk_header_s |
| { |
| uint16_t chunk_type; /* 0xcac1 -> raw; 0xcac2 -> fill; 0xcac3 -> don't care */ |
| uint16_t reserved1; |
| uint32_t chunk_sz; /* in blocks in output image */ |
| uint32_t total_sz; /* in bytes of chunk input file including chunk header and data */ |
| }; |
| |
| struct fastboot_mem_s |
| { |
| FAR void *addr; |
| }; |
| |
| struct fastboot_file_s |
| { |
| char path[PATH_MAX]; |
| off_t offset; |
| }; |
| |
| struct fastboot_transport_ops_s |
| { |
| CODE int (*init)(FAR struct fastboot_ctx_s *); |
| CODE void (*deinit)(FAR struct fastboot_ctx_s *); |
| CODE ssize_t (*read)(FAR struct fastboot_ctx_s *, FAR void *, size_t); |
| CODE int (*write)(FAR struct fastboot_ctx_s *, FAR const void *, size_t); |
| }; |
| |
| struct fastboot_ctx_s |
| { |
| /* Transport file descriptors |
| * |
| * | idx | USB | TCP | poll | |
| * |-----|----------|---------------|------| |
| * | 0 |usbdev in |TCP socket | Y | |
| * | 1 |usbdev out|accepted socket| N | |
| */ |
| |
| int tran_fd[2]; |
| int flash_fd; |
| size_t download_max; |
| size_t download_size; |
| size_t download_offset; |
| size_t total_imgsize; |
| |
| /* Store wait_ms argument before poll of fastboot_command_loop, and |
| * TCP transport remaining data size later. |
| */ |
| |
| uint64_t left; |
| FAR void *handle; |
| FAR void *download_buffer; |
| FAR struct fastboot_var_s *varlist; |
| CODE int (*upload_func)(FAR struct fastboot_ctx_s *); |
| FAR const struct fastboot_transport_ops_s *ops; |
| struct |
| { |
| size_t size; |
| union |
| { |
| struct fastboot_mem_s mem; |
| struct fastboot_file_s file; |
| } u; |
| } upload_param; |
| }; |
| |
| struct fastboot_cmd_s |
| { |
| FAR const char *prefix; |
| CODE void (*handle)(FAR struct fastboot_ctx_s *, FAR const char *); |
| }; |
| |
| typedef void (*memdump_print_t)(FAR void *, FAR const char *); |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| static void fastboot_getvar(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg); |
| static void fastboot_download(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg); |
| static void fastboot_erase(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg); |
| static void fastboot_flash(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg); |
| static void fastboot_reboot(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg); |
| static void fastboot_reboot_bootloader(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg); |
| static void fastboot_oem(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg); |
| static void fastboot_upload(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg); |
| |
| static void fastboot_memdump(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg); |
| static void fastboot_filedump(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg); |
| #ifdef CONFIG_SYSTEM_FASTBOOTD_SHELL |
| static void fastboot_shell(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg); |
| #endif |
| |
| /* USB transport */ |
| |
| #ifdef CONFIG_USBFASTBOOT |
| static int fastboot_usbdev_initialize(FAR struct fastboot_ctx_s *ctx); |
| static void fastboot_usbdev_deinit(FAR struct fastboot_ctx_s *ctx); |
| static ssize_t fastboot_usbdev_read(FAR struct fastboot_ctx_s *ctx, |
| FAR void *buf, size_t len); |
| static int fastboot_usbdev_write(FAR struct fastboot_ctx_s *ctx, |
| FAR const void *buf, size_t len); |
| #endif |
| |
| /* TCP transport */ |
| |
| #ifdef CONFIG_NET_TCP |
| static int fastboot_tcp_initialize(FAR struct fastboot_ctx_s *ctx); |
| static void fastboot_tcp_deinit(FAR struct fastboot_ctx_s *ctx); |
| static ssize_t fastboot_tcp_read(FAR struct fastboot_ctx_s *ctx, |
| FAR void *buf, size_t len); |
| static int fastboot_tcp_write(FAR struct fastboot_ctx_s *ctx, |
| FAR const void *buf, size_t len); |
| #endif |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static const struct fastboot_cmd_s g_fast_cmd[] = |
| { |
| { "getvar:", fastboot_getvar }, |
| { "download:", fastboot_download }, |
| { "erase:", fastboot_erase }, |
| { "flash:", fastboot_flash }, |
| { "reboot-bootloader", fastboot_reboot_bootloader}, |
| { "reboot", fastboot_reboot }, |
| { "oem", fastboot_oem }, |
| { "upload", fastboot_upload } |
| }; |
| |
| static const struct fastboot_cmd_s g_oem_cmd[] = |
| { |
| { "filedump", fastboot_filedump }, |
| { "memdump", fastboot_memdump }, |
| #ifdef CONFIG_SYSTEM_FASTBOOTD_SHELL |
| { "shell", fastboot_shell }, |
| #endif |
| }; |
| |
| #ifdef CONFIG_BOARD_MEMORY_RANGE |
| static const struct memory_region_s g_memory_region[] = |
| { |
| CONFIG_BOARD_MEMORY_RANGE |
| }; |
| #endif |
| |
| static const struct fastboot_transport_ops_s g_tran_ops[] = |
| { |
| #ifdef CONFIG_USBFASTBOOT |
| { |
| .init = fastboot_usbdev_initialize, |
| .deinit = fastboot_usbdev_deinit, |
| .read = fastboot_usbdev_read, |
| .write = fastboot_usbdev_write, |
| }, |
| #endif |
| #ifdef CONFIG_NET_TCP |
| { |
| .init = fastboot_tcp_initialize, |
| .deinit = fastboot_tcp_deinit, |
| .read = fastboot_tcp_read, |
| .write = fastboot_tcp_write, |
| }, |
| #endif |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| static FAR void *fastboot_memset32(FAR void *m, uint32_t val, size_t count) |
| { |
| FAR uint32_t *buf = m; |
| |
| while (count--) |
| { |
| *buf++ = val; |
| } |
| |
| return m; |
| } |
| |
| static ssize_t fastboot_read(int fd, FAR void *buf, size_t len) |
| { |
| ssize_t r = read(fd, buf, len); |
| return r < 0 ? -errno : r; |
| } |
| |
| static int fastboot_write(int fd, FAR const void *buf, size_t len) |
| { |
| FAR const char *data = buf; |
| |
| while (len > 0) |
| { |
| ssize_t r = write(fd, data, len); |
| if (r < 0) |
| { |
| return -errno; |
| } |
| |
| data += r; |
| len -= r; |
| } |
| |
| return OK; |
| } |
| |
| static void fastboot_ack(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *code, FAR const char *reason) |
| { |
| char response[FASTBOOT_MSG_LEN]; |
| |
| if (reason == NULL) |
| { |
| reason = ""; |
| } |
| |
| snprintf(response, FASTBOOT_MSG_LEN, "%s%s", code, reason); |
| ctx->ops->write(ctx, response, strlen(response)); |
| } |
| |
| static void fastboot_fail(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *fmt, ...) |
| { |
| char reason[FASTBOOT_MSG_LEN]; |
| va_list ap; |
| |
| va_start(ap, fmt); |
| vsnprintf(reason, sizeof(reason), fmt, ap); |
| fastboot_ack(ctx, "FAIL", reason); |
| va_end(ap); |
| } |
| |
| static void fastboot_okay(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *info) |
| { |
| fastboot_ack(ctx, "OKAY", info); |
| } |
| |
| static int fastboot_flash_open(FAR const char *name) |
| { |
| int fd = open(name, O_RDWR | O_CLOEXEC); |
| if (fd < 0) |
| { |
| fb_err("Open %s error\n", name); |
| return -errno; |
| } |
| |
| return fd; |
| } |
| |
| static void fastboot_flash_close(int fd) |
| { |
| if (fd >= 0) |
| { |
| fsync(fd); |
| close(fd); |
| } |
| } |
| |
| static int fastboot_flash_write(int fd, off_t offset, |
| FAR void *data, |
| size_t size) |
| { |
| int ret; |
| |
| offset = lseek(fd, offset, SEEK_SET); |
| if (offset < 0) |
| { |
| fb_err("Seek error:%d\n", errno); |
| return -errno; |
| } |
| |
| ret = fastboot_write(fd, data, size); |
| if (ret < 0) |
| { |
| fb_err("Flash write error:%d\n", -ret); |
| } |
| |
| return ret; |
| } |
| |
| static int ffastboot_flash_fill(int fd, off_t offset, |
| uint32_t fill_data, |
| uint32_t blk_sz, |
| uint32_t blk_num) |
| { |
| FAR void *buffer; |
| int ret = OK; |
| int i; |
| |
| buffer = malloc(blk_sz); |
| if (buffer == NULL) |
| { |
| fb_err("Flash bwrite malloc fail\n"); |
| return -ENOMEM; |
| } |
| |
| fastboot_memset32(buffer, fill_data, blk_sz / 4); |
| |
| for (i = 0; i < blk_num; i++) |
| { |
| ret = fastboot_flash_write(fd, offset, buffer, blk_sz); |
| if (ret < 0) |
| { |
| goto out; |
| } |
| |
| offset += blk_sz; |
| } |
| |
| out: |
| free(buffer); |
| return ret; |
| } |
| |
| static int fastboot_flash_erase(int fd) |
| { |
| int ret; |
| |
| ret = ioctl(fd, MTDIOC_BULKERASE, 0); |
| if (ret < 0) |
| { |
| fb_err("Erase device failed\n"); |
| } |
| |
| return ret < 0 ? -errno : ret; |
| } |
| |
| static int fastboot_flash_program(FAR struct fastboot_ctx_s *ctx, int fd) |
| { |
| FAR char *chunk_ptr = ctx->download_buffer; |
| FAR char *end_ptr = chunk_ptr + ctx->download_size; |
| FAR struct fastboot_sparse_header_s *sparse; |
| uint32_t chunk_num; |
| int ret = OK; |
| |
| /* No sparse header, write flash directly */ |
| |
| sparse = (FAR struct fastboot_sparse_header_s *)chunk_ptr; |
| if (sparse->magic != FASTBOOT_SPARSE_MAGIC) |
| { |
| ret = fastboot_flash_write(fd, 0, ctx->download_buffer, |
| ctx->download_size); |
| goto end; |
| } |
| |
| if (ctx->total_imgsize == 0) |
| { |
| ctx->total_imgsize = sparse->blk_sz * sparse->total_blks; |
| } |
| |
| chunk_num = sparse->total_chunks; |
| chunk_ptr += FASTBOOT_SPARSE_HEADER; |
| |
| while (chunk_ptr < end_ptr && chunk_num--) |
| { |
| FAR struct fastboot_chunk_header_s *chunk = |
| (FAR struct fastboot_chunk_header_s *)chunk_ptr; |
| |
| chunk_ptr += FASTBOOT_CHUNK_HEADER; |
| |
| switch (chunk->chunk_type) |
| { |
| case FASTBOOT_CHUNK_RAW: |
| { |
| uint32_t chunk_size = chunk->chunk_sz * sparse->blk_sz; |
| ret = fastboot_flash_write(fd, ctx->download_offset, |
| chunk_ptr, chunk_size); |
| if (ret < 0) |
| { |
| goto end; |
| } |
| |
| ctx->download_offset += chunk_size; |
| chunk_ptr += chunk_size; |
| } |
| break; |
| case FASTBOOT_CHUNK_FILL: |
| { |
| uint32_t fill_data = be32toh(*(FAR uint32_t *)chunk_ptr); |
| uint32_t chunk_size = chunk->chunk_sz * sparse->blk_sz; |
| ret = ffastboot_flash_fill(fd, ctx->download_offset, fill_data, |
| sparse->blk_sz, chunk->chunk_sz); |
| if (ret < 0) |
| { |
| goto end; |
| } |
| |
| ctx->download_offset += chunk_size; |
| chunk_ptr += 4; |
| } |
| break; |
| case FASTBOOT_CHUNK_DONT_CARE: |
| case FASTBOOT_CHUNK_CRC32: |
| break; |
| default: |
| fb_err("Error chunk type:%d, skip\n", chunk->chunk_type); |
| break; |
| } |
| } |
| |
| if (ctx->download_offset < ctx->total_imgsize) |
| { |
| return 1; |
| } |
| |
| end: |
| ctx->total_imgsize = 0; |
| ctx->download_offset = 0; |
| return ret; |
| } |
| |
| static void fastboot_flash(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg) |
| { |
| char blkdev[PATH_MAX]; |
| int ret; |
| |
| snprintf(blkdev, PATH_MAX, FASTBOOT_BLKDEV, arg); |
| |
| if (ctx->flash_fd < 0) |
| { |
| ctx->flash_fd = fastboot_flash_open(blkdev); |
| if (ctx->flash_fd < 0) |
| { |
| fastboot_fail(ctx, "Flash open failure"); |
| return; |
| } |
| } |
| |
| ret = fastboot_flash_program(ctx, ctx->flash_fd); |
| if (ret < 0) |
| { |
| fastboot_fail(ctx, "Image flash failure"); |
| } |
| else |
| { |
| fastboot_okay(ctx, ""); |
| } |
| |
| if (ret <= 0) |
| { |
| fastboot_flash_close(ctx->flash_fd); |
| ctx->flash_fd = -1; |
| } |
| } |
| |
| static void fastboot_erase(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg) |
| { |
| char blkdev[PATH_MAX]; |
| int ret; |
| int fd; |
| |
| snprintf(blkdev, PATH_MAX, FASTBOOT_BLKDEV, arg); |
| fb_info("Erase %s\n", blkdev); |
| |
| fd = fastboot_flash_open(blkdev); |
| if (fd < 0) |
| { |
| fastboot_fail(ctx, "Flash open failure"); |
| return; |
| } |
| |
| ret = fastboot_flash_erase(fd); |
| if (ret == -ENOTTY) |
| { |
| struct stat sb; |
| |
| ret = fstat(fd, &sb); |
| if (ret >= 0) |
| { |
| memset(ctx->download_buffer, 0xff, ctx->download_max); |
| |
| while (sb.st_size > 0) |
| { |
| size_t len = MIN(sb.st_size, ctx->download_max); |
| |
| ret = fastboot_write(fd, ctx->download_buffer, len); |
| if (ret < 0) |
| { |
| break; |
| } |
| |
| sb.st_size -= len; |
| } |
| } |
| } |
| |
| if (ret < 0) |
| { |
| fastboot_fail(ctx, "Flash erase failure"); |
| } |
| else |
| { |
| fastboot_okay(ctx, ""); |
| } |
| |
| fastboot_flash_close(fd); |
| } |
| |
| static void fastboot_download(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg) |
| { |
| FAR char *download; |
| char response[FASTBOOT_MSG_LEN]; |
| unsigned long len; |
| int ret; |
| |
| len = strtoul(arg, NULL, 16); |
| if (len > ctx->download_max) |
| { |
| fastboot_fail(ctx, "Data too large"); |
| return; |
| } |
| |
| snprintf(response, FASTBOOT_MSG_LEN, "DATA%08lx", len); |
| ret = ctx->ops->write(ctx, response, strlen(response)); |
| if (ret < 0) |
| { |
| fb_err("Response error [%d]\n", -ret); |
| return; |
| } |
| |
| download = ctx->download_buffer; |
| ctx->download_size = len; |
| |
| while (len > 0) |
| { |
| ssize_t r = ctx->ops->read(ctx, download, len); |
| if (r < 0) |
| { |
| if (errno == EAGAIN) |
| { |
| continue; |
| } |
| |
| ctx->download_size = 0; |
| fb_err("fastboot_download usb read error\n"); |
| return; |
| } |
| |
| len -= r; |
| download += r; |
| } |
| |
| fastboot_okay(ctx, ""); |
| } |
| |
| static void fastboot_getvar(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg) |
| { |
| FAR struct fastboot_var_s *var; |
| char buffer[FASTBOOT_MSG_LEN]; |
| |
| for (var = ctx->varlist; var != NULL; var = var->next) |
| { |
| if (!strcmp(var->name, arg)) |
| { |
| if (var->string == NULL) |
| { |
| itoa(var->data, buffer, 10); |
| fastboot_okay(ctx, buffer); |
| } |
| else |
| { |
| fastboot_okay(ctx, var->string); |
| } |
| |
| return; |
| } |
| } |
| |
| fastboot_okay(ctx, ""); |
| } |
| |
| static void fastboot_reboot(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg) |
| { |
| #ifdef CONFIG_BOARDCTL_RESET |
| fastboot_okay(ctx, ""); |
| boardctl(BOARDIOC_RESET, BOARDIOC_SOFTRESETCAUSE_USER_REBOOT); |
| #else |
| fastboot_fail(ctx, "Operation not supported"); |
| #endif |
| } |
| |
| static void fastboot_reboot_bootloader(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg) |
| { |
| #ifdef CONFIG_BOARDCTL_RESET |
| fastboot_okay(ctx, ""); |
| boardctl(BOARDIOC_RESET, BOARDIOC_SOFTRESETCAUSE_ENTER_BOOTLOADER); |
| #else |
| fastboot_fail(ctx, "Operation not supported"); |
| #endif |
| } |
| |
| static int fastboot_memdump_upload(FAR struct fastboot_ctx_s *ctx) |
| { |
| return ctx->ops->write(ctx, ctx->upload_param.u.mem.addr, |
| ctx->upload_param.size); |
| } |
| |
| static void fastboot_memdump_region(memdump_print_t memprint, FAR void *priv) |
| { |
| #ifdef CONFIG_BOARD_MEMORY_RANGE |
| char response[FASTBOOT_MSG_LEN - 4]; |
| size_t index; |
| |
| for (index = 0; index < nitems(g_memory_region); index++) |
| { |
| snprintf(response, sizeof(response), |
| "fastboot oem memdump 0x%" PRIxPTR " 0x%" PRIxPTR "\n", |
| g_memory_region[index].start, |
| g_memory_region[index].end - g_memory_region[index].start); |
| memprint(priv, response); |
| snprintf(response, sizeof(response), |
| "fastboot get_staged 0x%" PRIxPTR ".bin\n", |
| g_memory_region[index].start); |
| memprint(priv, response); |
| } |
| #endif |
| } |
| |
| static void fastboot_memdump_syslog(FAR void *priv, FAR const char *response) |
| { |
| fb_err(" %s", response); |
| } |
| |
| static void fastboot_memdump_response(FAR void *priv, |
| FAR const char *response) |
| { |
| fastboot_ack((FAR struct fastboot_ctx_s *)priv, "TEXT", response); |
| } |
| |
| /* Usage(host): |
| * fastboot oem memdump <addr> <size> |
| * |
| * Example |
| * fastboot oem memdump 0x44000000 0xb6c00 |
| * fastboot get_staged mem_44000000_440b6c00.bin |
| */ |
| |
| static void fastboot_memdump(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg) |
| { |
| if (!arg || |
| sscanf(arg, "%p %zx", &ctx->upload_param.u.mem.addr, |
| &ctx->upload_param.size) != 2) |
| { |
| fastboot_memdump_region(fastboot_memdump_response, ctx); |
| fastboot_fail(ctx, "Invalid argument"); |
| return; |
| } |
| |
| fb_info("Memdump Addr: %p, Size: 0x%zx\n", ctx->upload_param.u.mem.addr, |
| ctx->upload_param.size); |
| ctx->upload_func = fastboot_memdump_upload; |
| fastboot_okay(ctx, ""); |
| } |
| |
| static int fastboot_filedump_upload(FAR struct fastboot_ctx_s *ctx) |
| { |
| size_t size = ctx->upload_param.size; |
| int fd; |
| |
| fd = open(ctx->upload_param.u.file.path, O_RDONLY | O_CLOEXEC); |
| if (fd < 0) |
| { |
| fb_err("No such file or directory %d\n", errno); |
| return -errno; |
| } |
| |
| if (ctx->upload_param.u.file.offset && |
| lseek(fd, ctx->upload_param.u.file.offset, |
| ctx->upload_param.u.file.offset > 0 ? SEEK_SET : SEEK_END) < 0) |
| { |
| fb_err("Invalid argument, offset: %" PRIdOFF "\n", |
| ctx->upload_param.u.file.offset); |
| close(fd); |
| return -errno; |
| } |
| |
| while (size > 0) |
| { |
| ssize_t nread = fastboot_read(fd, ctx->download_buffer, |
| MIN(size, ctx->download_max)); |
| if (nread == 0) |
| { |
| break; |
| } |
| else if (nread < 0 || |
| ctx->ops->write(ctx, ctx->download_buffer, nread) < 0) |
| { |
| fb_err("Upload failed (%zu bytes left)\n", size); |
| close(fd); |
| return -errno; |
| } |
| |
| size -= nread; |
| } |
| |
| close(fd); |
| return 0; |
| } |
| |
| /* Usage(host): |
| * fastboot oem filedump <PATH> [<offset> <size>] |
| * |
| * Example |
| * a. Upload the entire file: |
| * fastboot oem filedump /dev/bootloader |
| * fastboot get_staged bl_all.bin |
| * |
| * b. Upload 4096 bytes of /dev/mem from offset 2048: |
| * fastboot oem filedump /dev/mem 2048 4096 |
| * fastboot get_staged bl_2048_6144.bin |
| * |
| * c. Get 2048 bytes from offset -1044480 |
| * fastboot oem "filedump /dev/bootloader -1044480 2048" |
| * fastboot get_staged bl_l1044480_l1042432.txt |
| */ |
| |
| static void fastboot_filedump(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg) |
| { |
| struct stat sb; |
| int ret; |
| |
| if (!arg) |
| { |
| fastboot_fail(ctx, "Invalid argument"); |
| return; |
| } |
| |
| ret = sscanf(arg, "%s %" PRIdOFF " %zu", ctx->upload_param.u.file.path, |
| &ctx->upload_param.u.file.offset, &ctx->upload_param.size); |
| if (ret != 1 && ret != 3) |
| { |
| fastboot_fail(ctx, "Failed to parse arguments"); |
| return; |
| } |
| else if (ret == 1) |
| { |
| ret = stat(ctx->upload_param.u.file.path, &sb); |
| if (ret < 0) |
| { |
| fastboot_fail(ctx, "No such file or directory"); |
| return; |
| } |
| |
| ctx->upload_param.size = sb.st_size; |
| ctx->upload_param.u.file.offset = 0; |
| } |
| |
| fb_info("Filedump Path: %s, Offset: %" PRIdOFF ", Size: %zu\n", |
| ctx->upload_param.u.file.path, ctx->upload_param.u.file.offset, |
| ctx->upload_param.size); |
| ctx->upload_func = fastboot_filedump_upload; |
| fastboot_okay(ctx, ""); |
| } |
| |
| #ifdef CONFIG_SYSTEM_FASTBOOTD_SHELL |
| static void fastboot_shell(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg) |
| { |
| char response[FASTBOOT_MSG_LEN - 4]; |
| FILE *fp; |
| int ret; |
| |
| fp = popen(arg, "r"); |
| if (fp == NULL) |
| { |
| fastboot_fail(ctx, "popen() fails %d", errno); |
| return; |
| } |
| |
| while (fgets(response, sizeof(response), fp)) |
| { |
| fastboot_ack(ctx, "TEXT", response); |
| } |
| |
| ret = pclose(fp); |
| if (WIFEXITED(ret) && WEXITSTATUS(ret) == 0) |
| { |
| fastboot_okay(ctx, ""); |
| return; |
| } |
| |
| fastboot_fail(ctx, "error detected 0x%x %d", ret, errno); |
| } |
| #endif |
| |
| static void fastboot_upload(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *arg) |
| { |
| char response[FASTBOOT_MSG_LEN]; |
| int ret; |
| |
| if (!ctx->upload_param.size || !ctx->upload_func) |
| { |
| fastboot_fail(ctx, "No data staged by the last command"); |
| return; |
| } |
| |
| snprintf(response, FASTBOOT_MSG_LEN, "DATA%08zx", ctx->upload_param.size); |
| |
| ret = ctx->ops->write(ctx, response, strlen(response)); |
| if (ret < 0) |
| { |
| fb_err("Response error [%d]\n", -ret); |
| goto done; |
| } |
| |
| ret = ctx->upload_func(ctx); |
| if (ret < 0) |
| { |
| fb_err("Upload failed, [%d]\n", -ret); |
| fastboot_fail(ctx, "Upload failed"); |
| } |
| else |
| { |
| fastboot_okay(ctx, ""); |
| } |
| |
| done: |
| ctx->upload_param.size = 0; |
| ctx->upload_func = NULL; |
| } |
| |
| static void fastboot_oem(FAR struct fastboot_ctx_s *ctx, FAR const char *arg) |
| { |
| size_t ncmds = nitems(g_oem_cmd); |
| size_t index; |
| |
| arg++; |
| |
| for (index = 0; index < ncmds; index++) |
| { |
| size_t len = strlen(g_oem_cmd[index].prefix); |
| if (memcmp(arg, g_oem_cmd[index].prefix, len) == 0) |
| { |
| arg += len; |
| g_oem_cmd[index].handle(ctx, *arg == ' ' ? ++arg : NULL); |
| break; |
| } |
| } |
| |
| if (index == ncmds) |
| { |
| fastboot_fail(ctx, "Unknown command"); |
| } |
| } |
| |
| static void fastboot_command_loop(FAR struct fastboot_ctx_s *ctx, |
| size_t nctx) |
| { |
| FAR struct fastboot_ctx_s *c; |
| struct epoll_event ev[nctx]; |
| int epfd; |
| int n; |
| |
| epfd = epoll_create(1); |
| if (epfd < 0) |
| { |
| fb_err("open epoll failed %d", errno); |
| return; |
| } |
| |
| for (c = ctx, n = nctx; n-- > 0; c++) |
| { |
| ev[n].events = EPOLLIN; |
| ev[n].data.ptr = c; |
| if (epoll_ctl(epfd, EPOLL_CTL_ADD, c->tran_fd[0], &ev[n]) < 0) |
| { |
| fb_err("err add poll %d", c->tran_fd[0]); |
| goto epoll_close; |
| } |
| } |
| |
| if (ctx->left > 0) |
| { |
| if (epoll_wait(epfd, ev, nitems(ev), ctx->left) <= 0) |
| { |
| goto epoll_close; |
| } |
| } |
| |
| /* Reinitialize for storing TCP transport remaining data size. */ |
| |
| for (c = ctx, n = nctx; n-- > 0; c++) |
| { |
| c->left = 0; |
| } |
| |
| while (1) |
| { |
| char buffer[FASTBOOT_MSG_LEN + 1]; |
| size_t ncmds = nitems(g_fast_cmd); |
| size_t index; |
| |
| n = epoll_wait(epfd, ev, nitems(ev), -1); |
| for (n--; n >= 0; ) |
| { |
| c = (FAR struct fastboot_ctx_s *)ev[n].data.ptr; |
| ssize_t r = c->ops->read(c, buffer, FASTBOOT_MSG_LEN); |
| if (r <= 0) |
| { |
| n--; |
| continue; |
| } |
| |
| buffer[r] = '\0'; |
| for (index = 0; index < ncmds; index++) |
| { |
| size_t len = strlen(g_fast_cmd[index].prefix); |
| if (memcmp(buffer, g_fast_cmd[index].prefix, len) == 0) |
| { |
| g_fast_cmd[index].handle(c, buffer + len); |
| break; |
| } |
| } |
| |
| if (index == ncmds) |
| { |
| fastboot_fail(c, "Unknown command"); |
| } |
| } |
| } |
| |
| epoll_close: |
| while (--c >= ctx) |
| { |
| epoll_ctl(epfd, EPOLL_CTL_DEL, c->tran_fd[0], NULL); |
| } |
| |
| close(epfd); |
| } |
| |
| static void fastboot_publish(FAR struct fastboot_ctx_s *ctx, |
| FAR const char *name, |
| FAR const char *string, |
| int data) |
| { |
| FAR struct fastboot_var_s *var; |
| |
| var = malloc(sizeof(*var)); |
| if (var == NULL) |
| { |
| fb_err("ERROR: Could not allocate the memory.\n"); |
| return; |
| } |
| |
| var->name = name; |
| var->string = string; |
| var->data = data; |
| var->next = ctx->varlist; |
| ctx->varlist = var; |
| } |
| |
| static void fastboot_create_publish(FAR struct fastboot_ctx_s *ctx, |
| size_t nctx) |
| { |
| for (; nctx-- > 0; ctx++) |
| { |
| fastboot_publish(ctx, "product", "NuttX", 0); |
| fastboot_publish(ctx, "kernel", "NuttX", 0); |
| fastboot_publish(ctx, "version", CONFIG_VERSION_STRING, 0); |
| fastboot_publish(ctx, "slot-count", "1", 0); |
| fastboot_publish(ctx, "max-download-size", NULL, |
| CONFIG_SYSTEM_FASTBOOTD_DOWNLOAD_MAX); |
| } |
| } |
| |
| static void fastboot_free_publish(FAR struct fastboot_ctx_s *ctx, |
| size_t nctx) |
| { |
| FAR struct fastboot_var_s *next; |
| |
| for (; nctx-- > 0; ctx++) |
| { |
| while (ctx->varlist) |
| { |
| next = ctx->varlist->next; |
| free(ctx->varlist); |
| ctx->varlist = next; |
| } |
| } |
| } |
| |
| #ifdef CONFIG_USBFASTBOOT |
| static int fastboot_open_usb(int index, int flags) |
| { |
| int try = FASTBOOT_EP_RETRY_TIMES; |
| char usbdev[32]; |
| int ret; |
| |
| snprintf(usbdev, sizeof(usbdev), |
| "%s/ep%d", FASTBOOT_USBDEV, index); |
| do |
| { |
| ret = open(usbdev, flags); |
| if (ret >= 0) |
| { |
| return ret; |
| } |
| |
| usleep(FASTBOOT_EP_RETRY_DELAY_MS * 1000); |
| } |
| while (try--); |
| |
| fb_err("open [%s] error %d\n", usbdev, errno); |
| |
| return -errno; |
| } |
| |
| static int fastboot_usbdev_initialize(FAR struct fastboot_ctx_s *ctx) |
| { |
| #ifdef CONFIG_SYSTEM_FASTBOOTD_USB_BOARDCTL |
| struct boardioc_usbdev_ctrl_s ctrl; |
| # ifdef CONFIG_USBDEV_COMPOSITE |
| uint8_t dev = BOARDIOC_USBDEV_COMPOSITE; |
| # else |
| uint8_t dev = BOARDIOC_USBDEV_FASTBOOT; |
| # endif |
| int ret; |
| |
| ctrl.usbdev = dev; |
| ctrl.action = BOARDIOC_USBDEV_INITIALIZE; |
| ctrl.instance = 0; |
| ctrl.config = 0; |
| ctrl.handle = NULL; |
| |
| ret = boardctl(BOARDIOC_USBDEV_CONTROL, (uintptr_t)&ctrl); |
| if (ret < 0) |
| { |
| fb_err("boardctl(BOARDIOC_USBDEV_INITIALIZE) failed: %d\n", ret); |
| return ret; |
| } |
| |
| ctrl.usbdev = dev; |
| ctrl.action = BOARDIOC_USBDEV_CONNECT; |
| ctrl.instance = 0; |
| ctrl.config = 0; |
| ctrl.handle = &ctx->handle; |
| |
| ret = boardctl(BOARDIOC_USBDEV_CONTROL, (uintptr_t)&ctrl); |
| if (ret < 0) |
| { |
| fb_err("boardctl(BOARDIOC_USBDEV_CONNECT) failed: %d\n", ret); |
| return ret; |
| } |
| #endif /* SYSTEM_FASTBOOTD_USB_BOARDCTL */ |
| |
| ctx->tran_fd[0] = fastboot_open_usb(FASTBOOT_EP_BULKOUT_IDX, |
| O_RDONLY | O_CLOEXEC | O_NONBLOCK); |
| if (ctx->tran_fd[0] < 0) |
| { |
| return ctx->tran_fd[0]; |
| } |
| |
| ctx->tran_fd[1] = fastboot_open_usb(FASTBOOT_EP_BULKIN_IDX, |
| O_WRONLY | O_CLOEXEC | O_NONBLOCK); |
| if (ctx->tran_fd[1] < 0) |
| { |
| close(ctx->tran_fd[0]); |
| ctx->tran_fd[0] = -1; |
| return ctx->tran_fd[1]; |
| } |
| |
| return 0; |
| } |
| |
| static void fastboot_usbdev_deinit(FAR struct fastboot_ctx_s *ctx) |
| { |
| #ifdef CONFIG_SYSTEM_FASTBOOTD_USB_BOARDCTL |
| struct boardioc_usbdev_ctrl_s ctrl; |
| #endif |
| int i; |
| |
| for (i = 0; i < nitems(ctx->tran_fd); i++) |
| { |
| close(ctx->tran_fd[i]); |
| ctx->tran_fd[i] = -1; |
| } |
| |
| #ifdef CONFIG_SYSTEM_FASTBOOTD_USB_BOARDCTL |
| if (ctx->handle) |
| { |
| # ifdef CONFIG_USBDEV_COMPOSITE |
| ctrl.usbdev = BOARDIOC_USBDEV_COMPOSITE; |
| # else |
| ctrl.usbdev = BOARDIOC_USBDEV_FASTBOOT; |
| # endif |
| ctrl.action = BOARDIOC_USBDEV_DISCONNECT; |
| ctrl.instance = 0; |
| ctrl.config = 0; |
| ctrl.handle = &ctx->handle; |
| |
| i = boardctl(BOARDIOC_USBDEV_CONTROL, (uintptr_t)&ctrl); |
| if (i < 0) |
| { |
| fb_err("boardctl(BOARDIOC_USBDEV_DISCONNECT) failed: %d\n", i); |
| } |
| } |
| #endif /* SYSTEM_FASTBOOTD_USB_BOARDCTL */ |
| } |
| |
| static ssize_t fastboot_usbdev_read(FAR struct fastboot_ctx_s *ctx, |
| FAR void *buf, size_t len) |
| { |
| return fastboot_read(ctx->tran_fd[0], buf, len); |
| } |
| |
| static int fastboot_usbdev_write(FAR struct fastboot_ctx_s *ctx, |
| FAR const void *buf, size_t len) |
| { |
| return fastboot_write(ctx->tran_fd[1], buf, len); |
| } |
| #endif |
| |
| #ifdef CONFIG_NET_TCP |
| static int fastboot_tcp_initialize(FAR struct fastboot_ctx_s *ctx) |
| { |
| struct sockaddr_in addr; |
| |
| ctx->tran_fd[0] = socket(AF_INET, SOCK_STREAM, |
| SOCK_CLOEXEC | SOCK_NONBLOCK); |
| if (ctx->tran_fd[0] < 0) |
| { |
| fb_err("create socket failed %d", errno); |
| return -errno; |
| } |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.sin_family = AF_INET; |
| addr.sin_addr.s_addr = htonl(INADDR_ANY); |
| addr.sin_port = htons(FASTBOOT_TCP_PORT); |
| if (bind(ctx->tran_fd[0], (struct sockaddr *) &addr, sizeof(addr)) < 0) |
| { |
| fb_err("bind() failed %d", errno); |
| goto error; |
| } |
| |
| if (listen(ctx->tran_fd[0], 1) < 0) |
| { |
| fb_err("listen() failed %d", errno); |
| goto error; |
| } |
| |
| return 0; |
| error: |
| close(ctx->tran_fd[0]); |
| ctx->tran_fd[0] = -1; |
| return -errno; |
| } |
| |
| static void fastboot_tcp_disconn(FAR struct fastboot_ctx_s *ctx) |
| { |
| close(ctx->tran_fd[1]); |
| ctx->tran_fd[1] = -1; |
| } |
| |
| static void fastboot_tcp_deinit(FAR struct fastboot_ctx_s *ctx) |
| { |
| fastboot_tcp_disconn(ctx); |
| close(ctx->tran_fd[0]); |
| ctx->tran_fd[0] = -1; |
| } |
| |
| static ssize_t fastboot_read_all(int fd, FAR void *buf, size_t len) |
| { |
| size_t total = 0; |
| ssize_t nread; |
| |
| while (total < len) |
| { |
| nread = fastboot_read(fd, buf, len); |
| if (nread <= 0) |
| { |
| if (total == 0) |
| { |
| return nread; |
| } |
| |
| break; |
| } |
| |
| total += nread; |
| } |
| |
| return total; |
| } |
| |
| static ssize_t fastboot_tcp_read(FAR struct fastboot_ctx_s *ctx, |
| FAR void *buf, size_t len) |
| { |
| char handshake[FASTBOOT_TCP_HANDSHAKE_LEN]; |
| uint64_t data_size; |
| ssize_t nread; |
| |
| if (ctx->tran_fd[1] == -1) |
| { |
| while (1) |
| { |
| /* Accept a connection, not care the address of the peer socket */ |
| |
| ctx->tran_fd[1] = accept(ctx->tran_fd[0], NULL, 0); |
| if (ctx->tran_fd[1] < 0) |
| { |
| continue; |
| } |
| |
| /* Handshake */ |
| |
| memset(handshake, 0, sizeof(handshake)); |
| if (fastboot_read_all(ctx->tran_fd[1], handshake, |
| sizeof(handshake)) != sizeof(handshake) || |
| strncmp(handshake, FASTBOOT_TCP_HANDSHAKE, |
| sizeof(handshake)) != 0 || |
| fastboot_write(ctx->tran_fd[1], handshake, |
| sizeof(handshake)) < 0) |
| { |
| fb_err("%s err handshake %d 0x%" PRIx32, __func__, errno, |
| *(FAR uint32_t *)handshake); |
| fastboot_tcp_disconn(ctx); |
| continue; |
| } |
| |
| break; |
| } |
| } |
| |
| if (ctx->left == 0) |
| { |
| nread = |
| fastboot_read_all(ctx->tran_fd[1], &data_size, sizeof(data_size)); |
| if (nread != sizeof(data_size)) |
| { |
| /* As normal, end of file if client has closed the connection */ |
| |
| if (nread != 0) |
| { |
| fb_err("%s err read data_size %zd %d", __func__, nread, errno); |
| } |
| |
| fastboot_tcp_disconn(ctx); |
| return nread; |
| } |
| |
| ctx->left = be64toh(data_size); |
| } |
| |
| if (len > ctx->left) |
| { |
| len = ctx->left; |
| } |
| |
| nread = fastboot_read(ctx->tran_fd[1], buf, len); |
| if (nread <= 0) |
| { |
| fastboot_tcp_disconn(ctx); |
| ctx->left = 0; |
| } |
| else |
| { |
| ctx->left -= nread; |
| } |
| |
| return nread; |
| } |
| |
| static int fastboot_tcp_write(FAR struct fastboot_ctx_s *ctx, |
| FAR const void *buf, size_t len) |
| { |
| uint64_t data_size = htobe64(len); |
| int ret; |
| |
| ret = fastboot_write(ctx->tran_fd[1], &data_size, sizeof(data_size)); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| return fastboot_write(ctx->tran_fd[1], buf, len); |
| } |
| #endif |
| |
| static int fastboot_context_initialize(FAR struct fastboot_ctx_s *ctx, |
| size_t nctx) |
| { |
| int ret; |
| |
| for (; nctx-- > 0; ctx++) |
| { |
| ctx->download_max = CONFIG_SYSTEM_FASTBOOTD_DOWNLOAD_MAX; |
| ctx->download_offset = 0; |
| ctx->download_size = 0; |
| ctx->flash_fd = -1; |
| ctx->total_imgsize = 0; |
| ctx->varlist = NULL; |
| ctx->left = ctx[0].left; |
| ctx->ops = &g_tran_ops[nctx]; |
| ctx->tran_fd[0] = -1; |
| ctx->tran_fd[1] = -1; |
| |
| ctx->download_buffer = malloc(CONFIG_SYSTEM_FASTBOOTD_DOWNLOAD_MAX); |
| if (ctx->download_buffer == NULL) |
| { |
| fb_err("ERROR: Could not allocate the memory.\n"); |
| continue; |
| } |
| |
| ret = ctx->ops->init(ctx); |
| if (ret < 0) |
| { |
| free(ctx->download_buffer); |
| ctx->download_buffer = NULL; |
| ctx->ops->deinit(ctx); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void fastboot_context_deinit(FAR struct fastboot_ctx_s *ctx, |
| size_t nctx) |
| { |
| for (; nctx-- > 0; ctx++) |
| { |
| if (ctx->download_buffer) |
| { |
| ctx->ops->deinit(ctx); |
| free(ctx->download_buffer); |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| int main(int argc, FAR char **argv) |
| { |
| struct fastboot_ctx_s context[nitems(g_tran_ops)]; |
| int ret; |
| |
| if (argc > 1) |
| { |
| if (strcmp(argv[1], "-h") == 0) |
| { |
| fb_err("Usage: fastbootd [wait_ms]\n"); |
| fb_err("\nmemdump: \n"); |
| fastboot_memdump_region(fastboot_memdump_syslog, NULL); |
| return 0; |
| } |
| |
| if (sscanf(argv[1], "%" SCNu64 , &context[0].left) != 1) |
| { |
| return -EINVAL; |
| } |
| } |
| |
| ret = fastboot_context_initialize(context, nitems(context)); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| fastboot_create_publish(context, nitems(context)); |
| fastboot_command_loop(context, nitems(context)); |
| fastboot_free_publish(context, nitems(context)); |
| fastboot_context_deinit(context, nitems(context)); |
| |
| return ret; |
| } |