platform_system_core/fastboot/protocol.c
Colin Cross f838788e6a fastboot: add support for auto-resparsing large files
Add support to fastboot for automatically using libsparse to break large
files, whether they are in sparse or normal format, into multiple sparse
files that can each fit into the target's memory.  Allows flashing
images that are larger than the size of the available memory on the
target.

By default, any file over 512MB will be sparsed into 512MB chunks.  The
limit can be modified with the -m argument, or sparsing can be forced
with -S or avoided with -N.  If -m is not specified, the target can
override the default by implementing getvar:max-download-size

Change-Id: I6c59381c3d24475c4f2587ea877200b96971cbd7
2012-07-09 22:17:58 -07:00

314 lines
7.4 KiB
C

/*
* Copyright (C) 2008 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#define min(a, b) \
({ typeof(a) _a = (a); typeof(b) _b = (b); (_a < _b) ? _a : _b; })
#define round_down(a, b) \
({ typeof(a) _a = (a); typeof(b) _b = (b); _a - (_a % _b); })
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sparse/sparse.h>
#include "fastboot.h"
static char ERROR[128];
char *fb_get_error(void)
{
return ERROR;
}
static int check_response(usb_handle *usb, unsigned int size, char *response)
{
unsigned char status[65];
int r;
for(;;) {
r = usb_read(usb, status, 64);
if(r < 0) {
sprintf(ERROR, "status read failed (%s)", strerror(errno));
usb_close(usb);
return -1;
}
status[r] = 0;
if(r < 4) {
sprintf(ERROR, "status malformed (%d bytes)", r);
usb_close(usb);
return -1;
}
if(!memcmp(status, "INFO", 4)) {
fprintf(stderr,"(bootloader) %s\n", status + 4);
continue;
}
if(!memcmp(status, "OKAY", 4)) {
if(response) {
strcpy(response, (char*) status + 4);
}
return 0;
}
if(!memcmp(status, "FAIL", 4)) {
if(r > 4) {
sprintf(ERROR, "remote: %s", status + 4);
} else {
strcpy(ERROR, "remote failure");
}
return -1;
}
if(!memcmp(status, "DATA", 4) && size > 0){
unsigned dsize = strtoul((char*) status + 4, 0, 16);
if(dsize > size) {
strcpy(ERROR, "data size too large");
usb_close(usb);
return -1;
}
return dsize;
}
strcpy(ERROR,"unknown status code");
usb_close(usb);
break;
}
return -1;
}
static int _command_start(usb_handle *usb, const char *cmd, unsigned size,
char *response)
{
int cmdsize = strlen(cmd);
int r;
if(response) {
response[0] = 0;
}
if(cmdsize > 64) {
sprintf(ERROR,"command too large");
return -1;
}
if(usb_write(usb, cmd, cmdsize) != cmdsize) {
sprintf(ERROR,"command write failed (%s)", strerror(errno));
usb_close(usb);
return -1;
}
return check_response(usb, size, response);
}
static int _command_data(usb_handle *usb, const void *data, unsigned size)
{
int r;
r = usb_write(usb, data, size);
if(r < 0) {
sprintf(ERROR, "data transfer failure (%s)", strerror(errno));
usb_close(usb);
return -1;
}
if(r != ((int) size)) {
sprintf(ERROR, "data transfer failure (short transfer)");
usb_close(usb);
return -1;
}
return r;
}
static int _command_end(usb_handle *usb)
{
int r;
r = check_response(usb, 0, 0);
if(r < 0) {
return -1;
}
return 0;
}
static int _command_send(usb_handle *usb, const char *cmd,
const void *data, unsigned size,
char *response)
{
int r;
if (size == 0) {
return -1;
}
r = _command_start(usb, cmd, size, response);
if (r < 0) {
return -1;
}
r = _command_data(usb, data, size);
if (r < 0) {
return -1;
}
r = _command_end(usb);
if(r < 0) {
return -1;
}
return size;
}
static int _command_send_no_data(usb_handle *usb, const char *cmd,
char *response)
{
int r;
return _command_start(usb, cmd, 0, response);
}
int fb_command(usb_handle *usb, const char *cmd)
{
return _command_send_no_data(usb, cmd, 0);
}
int fb_command_response(usb_handle *usb, const char *cmd, char *response)
{
return _command_send_no_data(usb, cmd, response);
}
int fb_download_data(usb_handle *usb, const void *data, unsigned size)
{
char cmd[64];
int r;
sprintf(cmd, "download:%08x", size);
r = _command_send(usb, cmd, data, size, 0);
if(r < 0) {
return -1;
} else {
return 0;
}
}
#define USB_BUF_SIZE 512
static char usb_buf[USB_BUF_SIZE];
static int usb_buf_len;
static int fb_download_data_sparse_write(void *priv, const void *data, int len)
{
int r;
usb_handle *usb = priv;
int to_write;
const char *ptr = data;
if (usb_buf_len) {
to_write = min(USB_BUF_SIZE - usb_buf_len, len);
memcpy(usb_buf + usb_buf_len, ptr, to_write);
usb_buf_len += to_write;
ptr += to_write;
len -= to_write;
}
if (usb_buf_len == USB_BUF_SIZE) {
r = _command_data(usb, usb_buf, USB_BUF_SIZE);
if (r != USB_BUF_SIZE) {
return -1;
}
usb_buf_len = 0;
}
if (len > USB_BUF_SIZE) {
if (usb_buf_len > 0) {
sprintf(ERROR, "internal error: usb_buf not empty\n");
return -1;
}
to_write = round_down(len, USB_BUF_SIZE);
r = _command_data(usb, ptr, to_write);
if (r != to_write) {
return -1;
}
ptr += to_write;
len -= to_write;
}
if (len > 0) {
if (len > USB_BUF_SIZE) {
sprintf(ERROR, "internal error: too much left for usb_buf\n");
return -1;
}
memcpy(usb_buf, ptr, len);
usb_buf_len = len;
}
return 0;
}
static int fb_download_data_sparse_flush(usb_handle *usb)
{
int r;
if (usb_buf_len > 0) {
r = _command_data(usb, usb_buf, usb_buf_len);
if (r != usb_buf_len) {
return -1;
}
usb_buf_len = 0;
}
return 0;
}
int fb_download_data_sparse(usb_handle *usb, struct sparse_file *s)
{
char cmd[64];
int r;
int size = sparse_file_len(s, true, false);
if (size <= 0) {
return -1;
}
sprintf(cmd, "download:%08x", size);
r = _command_start(usb, cmd, size, 0);
if (r < 0) {
return -1;
}
r = sparse_file_callback(s, true, false, fb_download_data_sparse_write, usb);
if (r < 0) {
return -1;
}
fb_download_data_sparse_flush(usb);
return _command_end(usb);
}