nuttx-apps/system/fastboot/fastboot.c

658 lines
17 KiB
C
Raw Normal View History

/****************************************************************************
* apps/system/fastboot/fastboot.c
*
* 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/version.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/boardctl.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/statfs.h>
#include <sys/types.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define FASTBOOT_USBDEV "/dev/fastboot"
#define FASTBOOT_BLKDEV "/dev/%s"
#define FASTBOOT_EP_BULKIN_IDX 0
#define FASTBOOT_EP_BULKOUT_IDX 1
#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)
#define FASTBOOT_GETUINT32(p) (((uint32_t)(p)[3] << 24) | \
((uint32_t)(p)[2] << 16) | \
((uint32_t)(p)[1] << 8) | \
(uint32_t)(p)[0])
/****************************************************************************
* Private types
****************************************************************************/
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 higer 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_ctx_s
{
int usbdev_in;
int usbdev_out;
size_t download_max;
size_t download_size;
size_t download_offset;
size_t total_imgsize;
FAR void *download_buffer;
FAR struct fastboot_var_s *varlist;
};
struct fastboot_cmd_s
{
FAR const char *prefix;
CODE void (*handle)(FAR struct fastboot_ctx_s *context,
FAR const char *arg);
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static void fastboot_getvar(FAR struct fastboot_ctx_s *context,
FAR const char *arg);
static void fastboot_download(FAR struct fastboot_ctx_s *context,
FAR const char *arg);
static void fastboot_erase(FAR struct fastboot_ctx_s *context,
FAR const char *arg);
static void fastboot_flash(FAR struct fastboot_ctx_s *context,
FAR const char *arg);
static void fastboot_reboot(FAR struct fastboot_ctx_s *context,
FAR const char *arg);
static void fastboot_reboot_bootloader(FAR struct fastboot_ctx_s *context,
FAR const char *arg);
/****************************************************************************
* 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 }
};
/****************************************************************************
* 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 void *buf, size_t len)
{
FAR 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 *context,
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);
fastboot_write(context->usbdev_out, response, strlen(response));
}
static void fastboot_fail(FAR struct fastboot_ctx_s *context,
FAR const char *reason)
{
fastboot_ack(context, "FAIL", reason);
}
static void fastboot_okay(FAR struct fastboot_ctx_s *context,
FAR const char *info)
{
fastboot_ack(context, "OKAY", info);
}
static int fastboot_flash_open(FAR const char *name)
{
int fd = open(name, O_RDWR);
if (fd < 0)
{
printf("Open %s error\n", name);
return -errno;
}
return fd;
}
static void fastboot_flash_close(int fd)
{
if (fd >= 0)
{
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)
{
printf("Seek error:%d\n", errno);
return -errno;
}
ret = fastboot_write(fd, data, size);
if (ret < 0)
{
printf("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)
{
printf("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)
{
return OK;
}
static int
fastboot_flash_program(FAR struct fastboot_ctx_s *context, int fd)
{
FAR char *chunk_ptr = context->download_buffer;
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)
{
return fastboot_flash_write(fd, 0, context->download_buffer,
context->download_size);
}
if (context->total_imgsize == 0)
{
context->total_imgsize = sparse->blk_sz * sparse->total_blks;
}
chunk_num = sparse->total_chunks;
chunk_ptr += FASTBOOT_SPARSE_HEADER;
while (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, context->download_offset,
chunk_ptr, chunk_size);
if (ret < 0)
{
return ret;
}
context->download_offset += chunk_size;
chunk_ptr += chunk_size;
}
break;
case FASTBOOT_CHUNK_FILL:
{
uint32_t fill_data = FASTBOOT_GETUINT32(chunk_ptr);
uint32_t chunk_size = chunk->chunk_sz * sparse->blk_sz;
ret = ffastboot_flash_fill(fd, context->download_offset,
fill_data, sparse->blk_sz,
chunk->chunk_sz);
if (ret < 0)
{
return ret;
}
context->download_offset += chunk_size;
chunk_ptr += 4;
}
break;
case FASTBOOT_CHUNK_DONT_CARE:
case FASTBOOT_CHUNK_CRC32:
break;
default:
printf("Error chunk type:%d, skip\n", chunk->chunk_type);
break;
}
}
if (context->download_offset >= context->total_imgsize)
{
context->total_imgsize = 0;
context->download_offset = 0;
}
return ret;
}
static void fastboot_flash(FAR struct fastboot_ctx_s *context,
FAR const char *arg)
{
char blkdev[PATH_MAX];
int fd;
snprintf(blkdev, PATH_MAX, FASTBOOT_BLKDEV, arg);
fd = fastboot_flash_open(blkdev);
if (fd < 0)
{
fastboot_fail(context, "Flash open failure");
return;
}
if (fastboot_flash_program(context, fd) < 0)
{
fastboot_fail(context, "Image flash failure");
}
else
{
fastboot_okay(context, "");
}
fastboot_flash_close(fd);
}
static void fastboot_erase(FAR struct fastboot_ctx_s *context,
FAR const char *arg)
{
char blkdev[PATH_MAX];
int fd;
snprintf(blkdev, PATH_MAX, FASTBOOT_BLKDEV, arg);
printf("Erase %s\n", blkdev);
fd = fastboot_flash_open(blkdev);
if (fd < 0)
{
fastboot_fail(context, "Flash open failure");
return;
}
if (fastboot_flash_erase(fd) < 0)
{
fastboot_fail(context, "Flash erase failure");
}
else
{
fastboot_okay(context, "");
}
fastboot_flash_close(fd);
}
static void fastboot_download(FAR struct fastboot_ctx_s *context,
FAR const char *arg)
{
FAR char *download;
char response[FASTBOOT_MSG_LEN];
unsigned long len;
int ret;
len = strtoul(arg, NULL, 16);
if (len > context->download_max)
{
fastboot_fail(context, "Data too large");
return;
}
snprintf(response, FASTBOOT_MSG_LEN, "DATA%08lx", len);
ret = fastboot_write(context->usbdev_out, response, strlen(response));
if (ret < 0)
{
printf("Reponse error [%d]\n", -ret);
return;
}
download = context->download_buffer;
while (len > 0)
{
ssize_t r = fastboot_read(context->usbdev_in,
download, len);
if (r < 0)
{
printf("fastboot_download usb read error\n");
return;
}
len -= r;
download += r;
}
context->download_size = len;
fastboot_okay(context, "");
}
static void fastboot_getvar(FAR struct fastboot_ctx_s *context,
FAR const char *arg)
{
FAR struct fastboot_var_s *var;
char buffer[FASTBOOT_MSG_LEN];
for (var = context->varlist; var != NULL; var = var->next)
{
if (!strcmp(var->name, arg))
{
if (var->string == NULL)
{
itoa(var->data, buffer, 10);
fastboot_okay(context, buffer);
}
else
{
fastboot_okay(context, var->string);
}
return;
}
}
fastboot_okay(context, "");
}
static void fastboot_reboot(FAR struct fastboot_ctx_s *context,
FAR const char *arg)
{
#ifdef CONFIG_BOARDCTL_RESET
boardctl(BOARDIOC_RESET, BOARDIOC_SOFTRESETCAUSE_USER_REBOOT);
#endif
}
static void fastboot_reboot_bootloader(FAR struct fastboot_ctx_s *context,
FAR const char *arg)
{
#ifdef CONFIG_BOARDCTL_RESET
boardctl(BOARDIOC_RESET, BOARDIOC_SOFTRESETCAUSE_ENTER_BOOTLOADER);
#endif
}
static void fastboot_command_loop(FAR struct fastboot_ctx_s *context)
{
while (1)
{
char buffer[FASTBOOT_MSG_LEN];
size_t ncmds = nitems(g_fast_cmd);
size_t index;
ssize_t r = fastboot_read(context->usbdev_in,
buffer, FASTBOOT_MSG_LEN);
if (r < 0)
{
printf("USB read error\n");
break;
}
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(context, buffer + len);
break;
}
}
if (index == ncmds)
{
fastboot_fail(context, "Unknown command");
}
}
}
static void fastboot_publish(FAR struct fastboot_ctx_s *context,
FAR const char *name,
FAR const char *string,
int data)
{
FAR struct fastboot_var_s *var;
var = malloc(sizeof(*var));
if (var)
{
var->name = name;
var->string = string;
var->data = data;
var->next = context->varlist;
context->varlist = var;
}
}
static void fastboot_create_publish(FAR struct fastboot_ctx_s *context)
{
fastboot_publish(context, "product", "NuttX", 0);
fastboot_publish(context, "kernel", "NuttX", 0);
fastboot_publish(context, "version", CONFIG_VERSION_STRING, 0);
fastboot_publish(context, "slot-count", "1", 0);
fastboot_publish(context, "max-download-size", NULL,
CONFIG_SYSTEM_FASTBOOTD_DOWNLOAD_MAX);
}
static void fastboot_free_publish(FAR struct fastboot_ctx_s *context)
{
FAR struct fastboot_var_s *next;
while (context->varlist)
{
next = context->varlist->next;
free(context->varlist);
context->varlist = next;
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, FAR char **argv)
{
FAR struct fastboot_ctx_s context;
FAR void *buffer = NULL;
char usbdev[32];
int ret = OK;
buffer = malloc(CONFIG_SYSTEM_FASTBOOTD_DOWNLOAD_MAX);
if (buffer == NULL)
{
printf("ERROR: Could not allocate the memory.\n");
return -ENOMEM;
}
snprintf(usbdev, sizeof(usbdev), "%s/ep%d",
FASTBOOT_USBDEV, FASTBOOT_EP_BULKOUT_IDX + 1);
context.usbdev_in = open(usbdev, O_RDONLY);
if (context.usbdev_in < 0)
{
printf("open [%s] error\n", usbdev);
ret = -errno;
goto err_with_mem;
}
snprintf(usbdev, sizeof(usbdev), "%s/ep%d",
FASTBOOT_USBDEV, FASTBOOT_EP_BULKIN_IDX + 1);
context.usbdev_out = open(usbdev, O_WRONLY);
if (context.usbdev_out < 0)
{
printf("open [%s] error\n", usbdev);
ret = -errno;
goto err_with_in;
}
context.download_buffer = buffer;
context.download_size = 0;
context.download_offset = 0;
context.download_max = CONFIG_SYSTEM_FASTBOOTD_DOWNLOAD_MAX;
fastboot_create_publish(&context);
fastboot_command_loop(&context);
fastboot_free_publish(&context);
close(context.usbdev_out);
context.usbdev_out = -1;
err_with_in:
close(context.usbdev_in);
context.usbdev_in = -1;
err_with_mem:
free(buffer);
return ret;
}