/*
 * Copyright 2008 - 2009 (C) Wind River Systems, Inc.
 * Tom Rix <Tom.Rix@windriver.com>
 *
 * Copyright (C) 2010-2014 Freescale Semiconductor, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 *
 * Part of the rx_handler were copied from the Android project.
 * Specifically rx command parsing in the  usb_rx_data_complete
 * function of the file bootable/bootloader/legacy/usbloader/usbloader.c
 *
 * The logical naming of flash comes from the Android project
 * Thse structures and functions that look like fastboot_flash_*
 * They come from bootable/bootloader/legacy/libboot/flash.c
 *
 * This is their Copyright:
 *
 * 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.
 */
#include <asm/byteorder.h>
#include <common.h>
#include <command.h>
#include <nand.h>
#include <fastboot.h>
#include <environment.h>
#include <mmc.h>

#if defined(CONFIG_OF_LIBFDT)
#include <libfdt.h>
#include <fdt_support.h>
#endif
#include <asm/bootm.h>

#ifdef CONFIG_FASTBOOT

int do_booti(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]);
extern int do_bootm_linux(int flag, int argc,
	char *argv[], bootm_headers_t *images);

/* Forward decl */
static int tx_handler(void);
static int rx_handler(const unsigned char *buffer, unsigned int buffer_size);
static void reset_handler(void);

static struct cmd_fastboot_interface interface = {
	.rx_handler            = rx_handler,
	.reset_handler         = reset_handler,
	.product_name          = NULL,
	.serial_no             = NULL,
	.nand_block_size       = 0,
	.transfer_buffer       = (unsigned char *)0xffffffff,
	.transfer_buffer_size  = 0,
};

static unsigned int download_size;
static unsigned int download_bytes;
static unsigned int download_bytes_unpadded;
static unsigned int download_error;
static unsigned int continue_booting;
static unsigned int upload_size;
static unsigned int upload_bytes;
static unsigned int upload_error;

#define MMC_SATA_BLOCK_SIZE 512

#ifdef CONFIG_FASTBOOT_STORAGE_NAND
static void save_env(struct fastboot_ptentry *ptn,
		     char *var, char *val)
{
#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
	char lock[128], unlock[128];
#endif

	setenv(var, val);

#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
	sprintf(lock, "nand lock 0x%x 0x%x", ptn->start, ptn->length);
	sprintf(unlock, "nand unlock 0x%x 0x%x", ptn->start, ptn->length);

	/* This could be a problem is there is an outstanding lock */
	run_command(unlock, 0);
#endif
	saveenv();

#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
	run_command(lock, 0);
#endif
}

void save_parts_values(struct fastboot_ptentry *ptn,
			      unsigned int offset,
			      unsigned int size)
{
	char var[64], val[32];
#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
	char lock[128], unlock[128];
	struct fastboot_ptentry *env_ptn;
#endif

	printf("saving it..\n");


	sprintf(var, "%s_nand_offset", ptn->name);
	sprintf(val, "0x%x", offset);

	printf("setenv %s %s\n", var, val);

	setenv(var, val);

	sprintf(var, "%s_nand_size", ptn->name);
	sprintf(val, "0x%x", size);

	printf("setenv %s %s\n", var, val);

	setenv(var, val);

#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
	/* Warning :
	   The environment is assumed to be in a partition named 'enviroment'.
	   It is very possible that your board stores the enviroment
	   someplace else. */
	env_ptn = fastboot_flash_find_ptn("environment");

	if (env_ptn) {
		sprintf(lock, "nand lock 0x%x 0x%x",
			env_ptn->start, env_ptn->length);
		sprintf(unlock, "nand unlock 0x%x 0x%x",
			env_ptn->start, env_ptn->length);

		run_command(unlock, 0);
	}
#endif
	saveenv();

#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
	if (env_ptn)
		run_command(lock, 0);
#endif
}

int check_parts_values(struct fastboot_ptentry *ptn)
{
	char var[64];

	sprintf(var, "%s_nand_offset", ptn->name);
	if (!getenv(var))
		return 1;

	sprintf(var, "%s_nand_size", ptn->name);
	if (!getenv(var))
		return 1;

	return 0;
}

static int write_to_ptn(struct fastboot_ptentry *ptn)
{
	int ret = 1;
	char length[32];
	char write_type[32];
	int repeat, repeat_max;

#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
	char lock[128];
	char unlock[128];
#endif
	char write[128];
	char erase[128];

	printf("flashing '%s'\n", ptn->name);

	/* Which flavor of write to use */
	if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_I)
		sprintf(write_type, "write.i");
#ifdef CONFIG_CMD_NAND_TRIMFFS
	else if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_TRIMFFS)
		sprintf(write_type, "write.trimffs");
#endif
	else
		sprintf(write_type, "write");

	/* Some flashing requires writing the same data in multiple,
	   consecutive flash partitions */
	repeat_max = 1;
	if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_REPEAT_MASK) {
		if (ptn->flags &
		    FASTBOOT_PTENTRY_FLAGS_WRITE_CONTIGUOUS_BLOCK) {
			printf("Warning can not do both 'contiguous block' "
				"and 'repeat' writes for for partition '%s'\n", ptn->name);
			printf("Ignoring repeat flag\n");
		} else {
			repeat_max = ptn->flags &
				FASTBOOT_PTENTRY_FLAGS_REPEAT_MASK;
		}
	}

	/* Unlock the whole partition instead of trying to
	   manage special cases */
	sprintf(length, "0x%x", ptn->length * repeat_max);

	for (repeat = 0; repeat < repeat_max; repeat++) {
#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
		sprintf(lock, "nand lock 0x%x %s",
			ptn->start + (repeat * ptn->length), length);
		sprintf(unlock, "nand unlock 0x%x %s",
			ptn->start + (repeat * ptn->length), length);
#endif
		sprintf(erase, "nand erase 0x%x %s",
			ptn->start + (repeat * ptn->length), length);

#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
		run_command(unlock, 0);
#endif
		run_command(erase, 0);

		if ((ptn->flags &
		     FASTBOOT_PTENTRY_FLAGS_WRITE_NEXT_GOOD_BLOCK) &&
		    (ptn->flags &
		     FASTBOOT_PTENTRY_FLAGS_WRITE_CONTIGUOUS_BLOCK)) {
			/* Both can not be true */
			printf("Warning can not do 'next good block' and \
				'contiguous block' for partition '%s'\n",
				ptn->name);
			printf("Ignoring these flags\n");
		} else if (ptn->flags &
			   FASTBOOT_PTENTRY_FLAGS_WRITE_NEXT_GOOD_BLOCK) {
			/* Keep writing until you get a good block
			   transfer_buffer should already be aligned */
			if (interface.nand_block_size) {
				unsigned int blocks = download_bytes /
					interface.nand_block_size;
				unsigned int i = 0;
				unsigned int offset = 0;

				while (i < blocks) {
					/* Check for overflow */
					if (offset >= ptn->length)
						break;

					/* download's address only advance
					   if last write was successful */

					/* nand's address always advances */
					sprintf(write, "nand %s 0x%p 0x%x 0x%x", write_type,
						interface.transfer_buffer +
						(i * interface.nand_block_size),
						ptn->start + (repeat * ptn->length) + offset,
						interface.nand_block_size);

					ret = run_command(write, 0);
					if (ret)
						break;
					else
						i++;

					/* Go to next nand block */
					offset += interface.nand_block_size;
				}
			} else {
				printf("Warning nand block size can not be 0 \
					when using 'next good block' for \
					partition '%s'\n", ptn->name);
				printf("Ignoring write request\n");
			}
		} else if (ptn->flags &
			 FASTBOOT_PTENTRY_FLAGS_WRITE_CONTIGUOUS_BLOCK) {
			/* Keep writing until you get a good block
			   transfer_buffer should already be aligned */
			if (interface.nand_block_size) {
				if (0 == nand_curr_device) {
					nand_info_t *nand;
					unsigned long off;
					unsigned int ok_start;

					nand = &nand_info[nand_curr_device];

					printf("\nDevice %d bad blocks:\n",
					       nand_curr_device);

					/* Initialize the ok_start to the
					   start of the partition
					   Then try to find a block large
					   enough for the download */
					ok_start = ptn->start;

					/* It is assumed that the start and
					   length are multiples of block size */
					for (off = ptn->start;
					     off < ptn->start + ptn->length;
					     off += nand->erasesize) {
						if (nand_block_isbad(nand, off)) {
							/* Reset the ok_start
							   to the next block */
							ok_start = off +
								nand->erasesize;
						}

						/* Check if we have enough
						   blocks */
						if ((ok_start - off) >=
						    download_bytes)
							break;
					}

					/* Check if there is enough space */
					if (ok_start + download_bytes <=
					    ptn->start + ptn->length) {

						sprintf(write, "nand %s 0x%p 0x%x 0x%x", write_type,
							interface.transfer_buffer,
							ok_start,
							download_bytes);

						ret = run_command(write, 0);

						/* Save the results into an
						   environment variable on the
						   format
						   ptn_name + 'offset'
						   ptn_name + 'size'  */
						if (ret) {
							/* failed */
							save_parts_values(ptn, ptn->start, 0);
						} else {
							/* success */
							save_parts_values(ptn, ok_start, download_bytes);
						}
					} else {
						printf("Error could not find enough contiguous space "
							"in partition '%s'\n", ptn->name);
						printf("Ignoring write request\n");
					}
				} else {
					/* TBD : Generalize flash handling */
					printf("Error only handling 1 NAND per board");
					printf("Ignoring write request\n");
				}
			} else {
				printf("Warning nand block size can not be 0 \
					when using 'continuous block' for \
					partition '%s'\n", ptn->name);
				printf("Ignoring write request\n");
			}
		} else {
			/* Normal case */
			sprintf(write, "nand %s 0x%p 0x%x 0x%x", write_type,
							interface.transfer_buffer,
							ptn->start + (repeat * ptn->length),
							download_bytes);
#ifdef CONFIG_CMD_NAND_TRIMFFS
			if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_TRIMFFS) {
				sprintf(write, "nand %s 0x%p 0x%x 0x%x", write_type,
							interface.transfer_buffer,
							ptn->start + (repeat * ptn->length),
							download_bytes_unpadded);
			}
#endif

			ret = run_command(write, 0);

			if (0 == repeat) {
				if (ret) /* failed */
					save_parts_values(ptn, ptn->start, 0);
				else     /* success */
					save_parts_values(ptn, ptn->start,
							  download_bytes);
			}
		}

#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
		run_command(lock, 0);
#endif

		if (ret)
			break;
	}

	return ret;
}
#else
static void save_env(struct fastboot_ptentry *ptn,
		     char *var, char *val)
{
	setenv(var, val);
	saveenv();
}
#endif

/* When save = 0, just parse.  The input is unchanged
   When save = 1, parse and do the save.  The input is changed */
static int parse_env(void *ptn, char *err_string, int save, int debug)
{
	int ret = 1;
	unsigned int sets = 0;
	unsigned int comment_start = 0;
	char *var = NULL;
	char *var_end = NULL;
	char *val = NULL;
	char *val_end = NULL;
	unsigned int i;

	char *buff = (char *)interface.transfer_buffer;
	unsigned int size = download_bytes_unpadded;

	/* The input does not have to be null terminated.
	   This will cause a problem in the corner case
	   where the last line does not have a new line.
	   Put a null after the end of the input.

	   WARNING : Input buffer is assumed to be bigger
	   than the size of the input */
	if (save)
		buff[size] = 0;

	for (i = 0; i < size; i++) {

		if (NULL == var) {

			/*
			 * Check for comments, comment ok only on
			 * mostly empty lines
			 */
			if (buff[i] == '#')
				comment_start = 1;

			if (comment_start) {
				if  ((buff[i] == '\r') ||
				     (buff[i] == '\n')) {
					comment_start = 0;
				}
			} else {
				if (!((buff[i] == ' ') ||
				      (buff[i] == '\t') ||
				      (buff[i] == '\r') ||
				      (buff[i] == '\n'))) {
					/*
					 * Normal whitespace before the
					 * variable
					 */
					var = &buff[i];
				}
			}

		} else if (((NULL == var_end) || (NULL == val)) &&
			   ((buff[i] == '\r') || (buff[i] == '\n'))) {

			/* This is the case when a variable
			   is unset. */

			if (save) {
				/* Set the var end to null so the
				   normal string routines will work

				   WARNING : This changes the input */
				buff[i] = '\0';

				save_env(ptn, var, val);

				if (debug)
					printf("Unsetting %s\n", var);
			}

			/* Clear the variable so state is parse is back
			   to initial. */
			var = NULL;
			var_end = NULL;
			sets++;
		} else if (NULL == var_end) {
			if ((buff[i] == ' ') ||
			    (buff[i] == '\t'))
				var_end = &buff[i];
		} else if (NULL == val) {
			if (!((buff[i] == ' ') ||
			      (buff[i] == '\t')))
				val = &buff[i];
		} else if (NULL == val_end) {
			if ((buff[i] == '\r') ||
			    (buff[i] == '\n')) {
				/* look for escaped cr or ln */
				if ('\\' == buff[i - 1]) {
					/* check for dos */
					if ((buff[i] == '\r') &&
					    (buff[i+1] == '\n'))
						buff[i + 1] = ' ';
					buff[i - 1] = buff[i] = ' ';
				} else {
					val_end = &buff[i];
				}
			}
		} else {
			sprintf(err_string, "Internal Error");

			if (debug)
				printf("Internal error at %s %d\n",
				       __FILE__, __LINE__);
			return 1;
		}
		/* Check if a var / val pair is ready */
		if (NULL != val_end) {
			if (save) {
				/* Set the end's with nulls so
				   normal string routines will
				   work.

				   WARNING : This changes the input */
				*var_end = '\0';
				*val_end = '\0';

				save_env(ptn, var, val);

				if (debug)
					printf("Setting %s %s\n", var, val);
			}

			/* Clear the variable so state is parse is back
			   to initial. */
			var = NULL;
			var_end = NULL;
			val = NULL;
			val_end = NULL;

			sets++;
		}
	}

	/* Corner case
	   Check for the case that no newline at end of the input */
	if ((NULL != var) &&
	    (NULL == val_end)) {
		if (save) {
			/* case of val / val pair */
			if (var_end)
				*var_end = '\0';
			/* else case handled by setting 0 past
			   the end of buffer.
			   Similar for val_end being null */
			save_env(ptn, var, val);

			if (debug) {
				if (var_end)
					printf("Trailing Setting %s %s\n", var, val);
				else
					printf("Trailing Unsetting %s\n", var);
			}
		}
		sets++;
	}
	/* Did we set anything ? */
	if (0 == sets)
		sprintf(err_string, "No variables set");
	else
		ret = 0;

	return ret;
}

static int saveenv_to_ptn(struct fastboot_ptentry *ptn, char *err_string)
{
	int ret = 1;
	int save = 0;
	int debug = 0;

	/* err_string is only 32 bytes
	   Initialize with a generic error message. */
	sprintf(err_string, "%s", "Unknown Error");

	/* Parse the input twice.
	   Only save to the enviroment if the entire input if correct */
	save = 0;
	if (0 == parse_env(ptn, err_string, save, debug)) {
		save = 1;
		ret = parse_env(ptn, err_string, save, debug);
	}
	return ret;
}


#if defined(CONFIG_FASTBOOT_STORAGE_NAND)

static void process_flash_nand(const char *cmdbuf, char *response)
{
	if (download_bytes) {
		struct fastboot_ptentry *ptn;

		ptn = fastboot_flash_find_ptn(cmdbuf + 6);
		if (ptn == 0) {
			sprintf(response, "FAILpartition does not exist");
		} else if ((download_bytes > ptn->length) &&
			   !(ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_ENV)) {
			sprintf(response, "FAILimage too large for partition");
			/* TODO : Improve check for yaffs write */
		} else {
			/* Check if this is not really a flash write
			   but rather a saveenv */
			if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_ENV) {
				/* Since the response can only be 64 bytes,
				   there is no point in having a large error message. */
				char err_string[32];
				if (saveenv_to_ptn(ptn, &err_string[0])) {
					printf("savenv '%s' failed : %s\n",
						ptn->name, err_string);
					sprintf(response, "FAIL%s", err_string);
				} else {
					printf("partition '%s' saveenv-ed\n", ptn->name);
					sprintf(response, "OKAY");
				}
			} else {
				/* Normal case */
				if (write_to_ptn(ptn)) {
					printf("flashing '%s' failed\n", ptn->name);
					sprintf(response, "FAILfailed to flash partition");
				} else {
					printf("partition '%s' flashed\n", ptn->name);
					sprintf(response, "OKAY");
				}
			}
		}
	} else {
		sprintf(response, "FAILno image downloaded");
	}

}
#endif

#if defined(CONFIG_FASTBOOT_STORAGE_SATA)
static void process_flash_sata(const char *cmdbuf, char *response)
{
	if (download_bytes) {
		struct fastboot_ptentry *ptn;

		/* Next is the partition name */
		ptn = fastboot_flash_find_ptn(cmdbuf + 6);
		if (ptn == 0) {
			printf("Partition:'%s' does not exist\n", ptn->name);
			sprintf(response, "FAILpartition does not exist");
		} else if ((download_bytes >
			   ptn->length * MMC_SATA_BLOCK_SIZE) &&
				!(ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_ENV)) {
			printf("Image too large for the partition\n");
			sprintf(response, "FAILimage too large for partition");
		} else if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_ENV) {
			/* Since the response can only be 64 bytes,
			   there is no point in having a large error message. */
			char err_string[32];
			if (saveenv_to_ptn(ptn, &err_string[0])) {
				printf("savenv '%s' failed : %s\n", ptn->name, err_string);
				sprintf(response, "FAIL%s", err_string);
			} else {
				printf("partition '%s' saveenv-ed\n", ptn->name);
				sprintf(response, "OKAY");
			}
		} else {
			unsigned int temp;
			char sata_write[128];

			/* block count */
			temp = (download_bytes +
				MMC_SATA_BLOCK_SIZE - 1) /
					MMC_SATA_BLOCK_SIZE;

			sprintf(sata_write, "sata write 0x%x 0x%x 0x%x",
				(unsigned int)interface.transfer_buffer,
				ptn->start,
				temp)

			if (run_command(sata_write, 0)) {
				printf("Writing '%s' FAILED!\n",
					 ptn->name);
				sprintf(response,
				       "FAIL: Write partition");
			} else {
				printf("Writing '%s' DONE!\n",
					ptn->name);
				sprintf(response, "OKAY");
			}
		}
	} else {
		sprintf(response, "FAILno image downloaded");
	}

}
#endif

#if defined(CONFIG_FASTBOOT_STORAGE_MMC)
static void process_flash_mmc(const char *cmdbuf, char *response)
{
	if (download_bytes) {
		struct fastboot_ptentry *ptn;

		/* Next is the partition name */
		ptn = fastboot_flash_find_ptn(cmdbuf + 6);
		if (ptn == 0) {
			printf("Partition:'%s' does not exist\n", ptn->name);
			sprintf(response, "FAILpartition does not exist");
		} else if ((download_bytes >
			   ptn->length * MMC_SATA_BLOCK_SIZE) &&
				!(ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_ENV)) {
			printf("Image too large for the partition\n");
			sprintf(response, "FAILimage too large for partition");
		} else if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_ENV) {
			/* Since the response can only be 64 bytes,
			   there is no point in having a large error message. */
			char err_string[32];
			if (saveenv_to_ptn(ptn, &err_string[0])) {
				printf("savenv '%s' failed : %s\n", ptn->name, err_string);
				sprintf(response, "FAIL%s", err_string);
			} else {
				printf("partition '%s' saveenv-ed\n", ptn->name);
				sprintf(response, "OKAY");
			}
		} else {
			unsigned int temp;

			char mmc_dev[128];
			char mmc_write[128];
			int mmcret;

			printf("writing to partition '%s'\n", ptn->name);

			if (ptn->partition_id != FASTBOOT_MMC_NONE_PARTITION_ID)
				sprintf(mmc_dev, "mmc dev %x %x",
					fastboot_devinfo.dev_id, /*slot no*/
					ptn->partition_id /*part no*/);
			else
				sprintf(mmc_dev, "mmc dev %x",
					fastboot_devinfo.dev_id /*slot no*/);

			/* block count */
			temp = (download_bytes +
				    MMC_SATA_BLOCK_SIZE - 1) /
				    MMC_SATA_BLOCK_SIZE;

			sprintf(mmc_write, "mmc write 0x%x 0x%x 0x%x",
					(unsigned int)interface.transfer_buffer, /*source*/
					ptn->start, /*dest*/
					temp /*length*/);

			printf("Initializing '%s'\n", ptn->name);

			mmcret = run_command(mmc_dev, 0);
			if (mmcret)
				sprintf(response, "FAIL:Init of MMC card");
			else
				sprintf(response, "OKAY");

			printf("Writing '%s'\n", ptn->name);
			if (run_command(mmc_write, 0)) {
				printf("Writing '%s' FAILED!\n", ptn->name);
				sprintf(response, "FAIL: Write partition");
			} else {
				printf("Writing '%s' DONE!\n", ptn->name);
				sprintf(response, "OKAY");
			}
		}
	} else {
		sprintf(response, "FAILno image downloaded");
	}
}

#endif

static void reset_handler ()
{
	/* If there was a download going on, bail */
	download_size = 0;
	download_bytes = 0;
	download_bytes_unpadded = 0;
	download_error = 0;
	continue_booting = 0;
	upload_size = 0;
	upload_bytes = 0;
	upload_error = 0;
}

static void rx_process_getvar(const char *cmdbuf, char *response)
{
	int temp_len = 0;

	strcpy(response, "OKAY");

	temp_len = strlen("getvar:");
	if (!strcmp(cmdbuf + temp_len, "version")) {
		strcpy(response + 4, FASTBOOT_VERSION);
	} else if (!strcmp(cmdbuf + temp_len,
			     "product")) {
		if (interface.product_name)
			strcpy(response + 4, interface.product_name);

	} else if (!strcmp(cmdbuf + temp_len,
			     "serialno")) {
		if (interface.serial_no)
			strcpy(response + 4, interface.serial_no);

	} else if (!strcmp(cmdbuf + temp_len,
			    "downloadsize")) {
		if (interface.transfer_buffer_size)
			sprintf(response + 4, "0x%x",
				interface.transfer_buffer_size);
	} else {
		fastboot_getvar(cmdbuf + 7, response + 4);
	}
}

static void rx_process_reboot(const char *cmdbuf, char *response)
{
	sprintf(response, "OKAY");
	fastboot_tx_status(response, strlen(response));
	udelay(1000000); /* 1 sec */

	do_reset(NULL, 0, 0, NULL);
}

static int rx_process_erase(const char *cmdbuf, char *response)
{
#if defined(CONFIG_FASTBOOT_STORAGE_NAND)
	struct fastboot_ptentry *ptn;

	ptn = fastboot_flash_find_ptn(cmdbuf + 6);
	if (ptn == 0) {
		sprintf(response, "FAILpartition does not exist");
	} else {
		int status, repeat, repeat_max;

		printf("erasing '%s'\n", ptn->name);

#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
		char lock[128];
		char unlock[128];
#endif
		char erase[128];

		repeat_max = 1;
		if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_REPEAT_MASK)
			repeat_max = ptn->flags & FASTBOOT_PTENTRY_FLAGS_REPEAT_MASK;

		for (repeat = 0; repeat < repeat_max;
			repeat++) {
#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
			sprintf(lock, "nand lock 0x%x 0x%x",
				ptn->start + (repeat * ptn->length),
				ptn->length);
			sprintf(unlock, "nand unlock 0x%x 0x%x",
				ptn->start + (repeat * ptn->length),
				ptn->length);
#endif
			sprintf(erase, "nand erase 0x%x 0x%x",
				ptn->start + (repeat * ptn->length),
				ptn->length);

#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
			run_command(unlock, 0);
#endif
			status = run_command(erase, 0);
#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
			run_command(lock, 0);
#endif

			if (status)
				break;
		}

		if (status) {
			sprintf(response,
				   "FAILfailed to erase partition");
		} else {
			printf("partition '%s' erased\n", ptn->name);
			sprintf(response, "OKAY");
		}
	}
	return 0;
#else
	printf("Not support erase command for EMMC\n");
	return -1;
#endif

}

static void rx_process_flash(const char *cmdbuf, char *response)
{
	switch (fastboot_devinfo.type) {
#if defined(CONFIG_FASTBOOT_STORAGE_SATA)
	case DEV_SATA:
		process_flash_sata(cmdbuf, response);
		break;
#endif
#if defined(CONFIG_FASTBOOT_STORAGE_MMC)
	case DEV_MMC:
		process_flash_mmc(cmdbuf, response);
		break;
#endif
#if defined(CONFIG_FASTBOOT_STORAGE_NAND)
	case DEV_NAND:
		process_flash_nand(cmdbuf, response);
		break;
#endif
	default:
		printf("Not support flash command for current device %d\n",
			fastboot_devinfo.type);
		sprintf(response,
			   "FAILfailed to flash device");
		break;
	}
}

static void rx_process_boot(const char *cmdbuf, char *response)
{
	if ((download_bytes) &&
		(CFG_FASTBOOT_MKBOOTIMAGE_PAGE_SIZE <
			download_bytes)) {
		char start[32];
		char *booti_args[4] = {"booti",  NULL, "boot", NULL};

		/*
		 * Use this later to determine if a command line was passed
		 * for the kernel.
		 */
		/* struct fastboot_boot_img_hdr *fb_hdr = */
		/*	(struct fastboot_boot_img_hdr *) interface.transfer_buffer; */

		/* Skip the mkbootimage header */
		/* image_header_t *hdr = */
		/*	(image_header_t *) */
		/*	&interface.transfer_buffer[CFG_FASTBOOT_MKBOOTIMAGE_PAGE_SIZE]; */

		booti_args[1] = start;
		sprintf(start, "0x%x", (unsigned int)interface.transfer_buffer);

		/* Execution should jump to kernel so send the response
		   now and wait a bit.	*/
		sprintf(response, "OKAY");
		fastboot_tx_status(response, strlen(response));

		printf("Booting kernel...\n");


		/* Reserve for further use, this can
		 * be more convient for developer. */
		/* if (strlen ((char *) &fb_hdr->cmdline[0])) */
		/*	set_env("bootargs", (char *) &fb_hdr->cmdline[0]); */

		/* boot the boot.img */
		do_booti(NULL, 0, 3, booti_args);


	}
	sprintf(response, "FAILinvalid boot image");
}

static void rx_process_upload(const char *cmdbuf, char *response)
{
#if defined(CONFIG_FASTBOOT_STORAGE_NAND)
	unsigned int adv, delim_index, len;
	struct fastboot_ptentry *ptn;
	unsigned int is_raw = 0;

	/* Is this a raw read ? */
	if (memcmp(cmdbuf, "uploadraw:", 10) == 0) {
		is_raw = 1;
		adv = 10;
	} else {
		adv = 7;
	}

	/* Scan to the next ':' to find when the size starts */
	len = strlen(cmdbuf);
	for (delim_index = adv;
		 delim_index < len; delim_index++) {
		if (cmdbuf[delim_index] == ':') {
			/* WARNING, cmdbuf is being modified. */
			*((char *) &cmdbuf[delim_index]) = 0;
			break;
		}
	}

	ptn = fastboot_flash_find_ptn(cmdbuf + adv);
	if (ptn == 0) {
		sprintf(response,
			"FAILpartition does not exist");
	} else {
		/* This is how much the user is expecting */
		unsigned int user_size;
		/*
		 * This is the maximum size needed for
		 * this partition
		 */
		unsigned int size;
		/* This is the length of the data */
		unsigned int length;
		/*
		 * Used to check previous write of
		 * the parition
		 */
		char env_ptn_length_var[128];
		char *env_ptn_length_val;

		user_size = 0;
		if (delim_index < len)
			user_size =
				simple_strtoul(cmdbuf + delim_index + 1,
					NULL, 16);

		/* Make sure output is padded to block size */
		length = ptn->length;
		sprintf(env_ptn_length_var,
			"%s_nand_size", ptn->name);
		env_ptn_length_val = getenv(env_ptn_length_var);
		if (env_ptn_length_val) {
			length =
				simple_strtoul(env_ptn_length_val,
					NULL, 16);
			/* Catch possible problems */
			if (!length)
				length = ptn->length;
		}

		size = length / interface.nand_block_size;
		size *= interface.nand_block_size;
		if (length % interface.nand_block_size)
			size += interface.nand_block_size;

		if (is_raw)
			size += (size /
				 interface.nand_block_size) *
				interface.nand_oob_size;

		if (size > interface.transfer_buffer_size) {

			sprintf(response, "FAILdata too large");

		} else if (user_size == 0) {

			/* Send the data response */
			sprintf(response, "DATA%08x", size);

		} else if (user_size != size) {
			/* This is the wrong size */
			sprintf(response, "FAIL");
		} else {
			/*
			 * This is where the transfer
			 * buffer is populated
			 */
			unsigned char *buf =
			  interface.transfer_buffer;
			char read[128];

			/*
			 * Setting upload_size causes
			 * transfer to happen in main loop
			 */
			upload_size = size;
			upload_bytes = 0;
			upload_error = 0;

			/*
			 * Poison the transfer buffer, 0xff
			 * is erase value of nand
			 */
			memset(buf, 0xff, upload_size);

			/* Which flavor of read to use */
			if (is_raw)
				sprintf(read, "nand read.raw 0x%x 0x%x 0x%x",
					(unsigned int)(interface.transfer_buffer),
					ptn->start,
					upload_size);
			else
				sprintf(read, "nand read.i 0x%x 0x%x 0x%x",
					(unsigned int)(interface.transfer_buffer),
					ptn->start,
					upload_size);

			run_command(read, 0);

			/* Send the data response */
			sprintf(response, "DATA%08x", size);
		}
	}
#endif

}

static int tx_handler(void)
{
	if (upload_size) {

		int bytes_written;
		bytes_written = fastboot_tx(interface.transfer_buffer +
					    upload_bytes, upload_size -
					    upload_bytes);
		if (bytes_written > 0) {

			upload_bytes += bytes_written;
			/* Check if this is the last */
			if (upload_bytes == upload_size) {

				/* Reset upload */
				upload_size = 0;
				upload_bytes = 0;
				upload_error = 0;
			}
		}
	}
	return upload_error;
}

static int rx_handler (const unsigned char *buffer, unsigned int buffer_size)
{
	int ret = 1;

	/*response buffer, Use 65 instead of 64
	   null gets dropped. strcpy's need the extra byte */
	char response[FASTBOOT_RESPONSE_SIZE];

	if (download_size) {
		/* Something to download */

		if (buffer_size) {
			/* Handle possible overflow */
			unsigned int transfer_size =
				download_size - download_bytes;

			if (buffer_size < transfer_size)
				transfer_size = buffer_size;

			/* Save the data to the transfer buffer */
			memcpy(interface.transfer_buffer + download_bytes,
				buffer, transfer_size);

			download_bytes += transfer_size;

			/* Check if transfer is done */
			if (download_bytes >= download_size) {
				/* Reset global transfer variable,
				   Keep download_bytes because it will be
				   used in the next possible flashing command */
				download_size = 0;

				if (download_error) {
					/* There was an earlier error */
					sprintf(response, "ERROR");
				} else {
					/* Everything has transferred,
					   send the OK response */
					sprintf(response, "OKAY");
				}
				fastboot_tx_status(response, strlen(response));

				printf("\ndownloading of %d bytes finished\n",
					download_bytes);

#if defined(CONFIG_FASTBOOT_STORAGE_NAND)
				/* Pad to block length
				   In most cases, padding the download to be
				   block aligned is correct. The exception is
				   when the following flash writes to the oob
				   area.  This happens when the image is a
				   YAFFS image.  Since we do not know what
				   the download is until it is flashed,
				   go ahead and pad it, but save the true
				   size in case if should have
				   been unpadded */
				download_bytes_unpadded = download_bytes;
				if (interface.nand_block_size) {
					if (download_bytes %
					    interface.nand_block_size) {
						unsigned int pad =
							interface.nand_block_size -
							(download_bytes % interface.nand_block_size);
						unsigned int i;

						for (i = 0; i < pad; i++) {
							if (download_bytes >=
								interface.transfer_buffer_size)
								break;

							interface.transfer_buffer[download_bytes] = 0;
							download_bytes++;
						}
					}
				}
#endif
			}

			/* Provide some feedback */
			if (download_bytes &&
			    0 == (download_bytes %
				  (16 * interface.nand_block_size))) {
				/* Some feeback that the
				   download is happening */
				if (download_error)
					printf("X");
				else
					printf(".");
				if (0 == (download_bytes %
					  (80 * 16 *
					   interface.nand_block_size)))
					printf("\n");

			}
		} else {
			/* Ignore empty buffers */
			printf("Warning empty download buffer\n");
			printf("Ignoring\n");
		}
		ret = 0;
	} else {
		/* A command */

		/* Cast to make compiler happy with string functions */
		const char *cmdbuf = (char *) buffer;
		printf("cmdbuf: %s\n", cmdbuf);

		/* Generic failed response */
		sprintf(response, "FAIL");

		/* reboot
		   Reboot the board. */
		if (memcmp(cmdbuf, "reboot", 6) == 0) {
			rx_process_reboot(cmdbuf, response);
			/* This code is unreachable,
			   leave it to make the compiler happy */
			return 0;
		}

		/* getvar
		   Get common fastboot variables
		   Board has a chance to handle other variables */
		if (memcmp(cmdbuf, "getvar:", 7) == 0) {
			rx_process_getvar(cmdbuf, response);
			ret = 0;
		}

		/* erase
		   Erase a register flash partition
		   Board has to set up flash partitions */
		if (memcmp(cmdbuf, "erase:", 6) == 0)
			ret = rx_process_erase(cmdbuf, response);

		/* download
		   download something ..
		   What happens to it depends on the next command after data */

		if (memcmp(cmdbuf, "download:", 9) == 0) {

			/* save the size */
			download_size = simple_strtoul(cmdbuf + 9, NULL, 16);
			/* Reset the bytes count, now it is safe */
			download_bytes = 0;
			/* Reset error */
			download_error = 0;

			printf("Starting download of %d bytes\n",
				download_size);

			if (0 == download_size) {
				/* bad user input */
				sprintf(response, "FAILdata invalid size");
			} else if (download_size >
				    interface.transfer_buffer_size) {
				/* set download_size to 0 because this is an error */
				download_size = 0;
				sprintf(response, "FAILdata too large");
			} else {
				/* The default case, the transfer fits
				   completely in the interface buffer */
				sprintf(response, "DATA%08x", download_size);
			}
			ret = 0;
		}

		/* boot
		   boot what was downloaded

		   WARNING WARNING WARNING

		   This is not what you expect.
		   The fastboot client does its own packaging of the
		   kernel.  The layout is defined in the android header
		   file bootimage.h.  This layeout is copiedlooks like this,

		   **
		   ** +-----------------+
		   ** | boot header     | 1 page
		   ** +-----------------+
		   ** | kernel          | n pages
		   ** +-----------------+
		   ** | ramdisk         | m pages
		   ** +-----------------+
		   ** | second stage    | o pages
		   ** +-----------------+
		   **

		   We only care about the kernel.
		   So we have to jump past a page.

		   What is a page size ?
		   The fastboot client uses 2048

		   The is the default value of

		   CFG_FASTBOOT_MKBOOTIMAGE_PAGE_SIZE

		*/

		if (memcmp(cmdbuf, "boot", 4) == 0) {
			rx_process_boot(cmdbuf, response);
			ret = 0;
		}

		/* flash
		   Flash what was downloaded */
		if (memcmp(cmdbuf, "flash:", 6) == 0) {
			rx_process_flash(cmdbuf, response);
			ret = 0;
		}

		/* continue
		   Stop doing fastboot */
		if (memcmp(cmdbuf, "continue", 8) == 0) {
			sprintf(response, "OKAY");
			continue_booting = 1;
			ret = 0;
		}

		/* upload
		   Upload just the data in a partition */
		if ((memcmp(cmdbuf, "upload:", 7) == 0) ||
		    (memcmp(cmdbuf, "uploadraw:", 10) == 0)) {
			rx_process_upload(cmdbuf, response);
			ret = 0;
		}

		fastboot_tx_status(response, strlen(response));

	} /* End of command */

	return ret;
}

int do_fastboot(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	int ret = 1;
	int check_timeout = 0;
	uint64_t timeout_endtime = 0;
	uint64_t timeout_ticks = 1000;
	int continue_from_disconnect = 0;

	do {
		continue_from_disconnect = 0;

		/* Initialize the board specific support */
		if (0 == fastboot_init(&interface)) {

			int poll_status;

			/* If we got this far, we are a success */
			ret = 0;
			printf("fastboot initialized\n");

			timeout_endtime = get_timer(0);
			timeout_endtime += timeout_ticks;

			while (1) {
				uint64_t current_time = 0;
				poll_status = fastboot_poll();

				if (1 == check_timeout)
					current_time = get_timer(0);

				if (FASTBOOT_ERROR == poll_status) {
					/* Error */
					break;
				} else if (FASTBOOT_DISCONNECT == poll_status) {
					/* beak, cleanup and re-init */
					printf("Fastboot disconnect detected\n");
					continue_from_disconnect = 1;
					break;
				} else if ((1 == check_timeout) &&
					   (FASTBOOT_INACTIVE == poll_status)) {

					/* No activity */
					if (current_time >= timeout_endtime) {
						printf("Fastboot inactivity detected\n");
						break;
					}
				} else {
					/* Something happened */
					if (1 == check_timeout) {
						/* Update the timeout endtime */
						timeout_endtime = current_time;
						timeout_endtime += timeout_ticks;
					}
				}

				/* Check if the user wanted to terminate with ^C */
				if ((FASTBOOT_INACTIVE == poll_status) &&
				    (ctrlc())) {
					printf("Fastboot ended by user\n");
					break;
				}

				/*
				 * Check if the fastboot client wanted to
				 * continue booting
				 */
				if (continue_booting) {
					printf("Fastboot ended by client\n");
					break;
				}

				/* Check if there is something to upload */
				tx_handler();
			}
		}

		/* Reset the board specific support */
		fastboot_shutdown();

		/* restart the loop if a disconnect was detected */
	} while (continue_from_disconnect);

	return ret;
}

U_BOOT_CMD(
	fastboot,	2,	1,	do_fastboot,
	"fastboot- use USB Fastboot protocol\n",
	"[inactive timeout]\n"
	"    - Run as a fastboot usb device.\n"
	"    - The optional inactive timeout is the decimal seconds before\n"
	"    - the normal console resumes\n"
);


#ifdef CONFIG_CMD_BOOTI
  /* Section for Android bootimage format support
   * Refer:
   * http://android.git.kernel.org/?p=platform/system/core.git;a=blob;
   * f=mkbootimg/bootimg.h
   */

void
bootimg_print_image_hdr(struct fastboot_boot_img_hdr *hdr)
{
#ifdef DEBUG
	int i;
	printf("   Image magic:   %s\n", hdr->magic);

	printf("   kernel_size:   0x%x\n", hdr->kernel_size);
	printf("   kernel_addr:   0x%x\n", hdr->kernel_addr);

	printf("   rdisk_size:   0x%x\n", hdr->ramdisk_size);
	printf("   rdisk_addr:   0x%x\n", hdr->ramdisk_addr);

	printf("   second_size:   0x%x\n", hdr->second_size);
	printf("   second_addr:   0x%x\n", hdr->second_addr);

	printf("   tags_addr:   0x%x\n", hdr->tags_addr);
	printf("   page_size:   0x%x\n", hdr->page_size);

	printf("   name:      %s\n", hdr->name);
	printf("   cmdline:   %s%x\n", hdr->cmdline);

	for (i = 0; i < 8; i++)
		printf("   id[%d]:   0x%x\n", i, hdr->id[i]);
#endif
}

static struct fastboot_boot_img_hdr boothdr __aligned(ARCH_DMA_MINALIGN);

#define ALIGN_SECTOR(n, pagesz) ((n + (pagesz - 1)) & (~(pagesz - 1)))

#ifdef CONFIG_LMB
static void boot_start_lmb(bootm_headers_t *images)
{
	ulong		mem_start;
	phys_size_t	mem_size;

	lmb_init(&images->lmb);

	mem_start = getenv_bootm_low();
	mem_size = getenv_bootm_size();

	lmb_add(&images->lmb, (phys_addr_t)mem_start, mem_size);

	arch_lmb_reserve(&images->lmb);
	board_lmb_reserve(&images->lmb);
}
#else
#define lmb_reserve(lmb, base, size)
static inline void boot_start_lmb(bootm_headers_t *images) { }
#endif

/* Allow for arch specific config before we boot */
static void __arch_preboot_os(void)
{
	/* please define platform specific arch_preboot_os() */
}
void arch_preboot_os(void) __attribute__((weak, alias("__arch_preboot_os")));

/* booti <addr> [ mmc0 | mmc1 [ <partition> ] ] */
int do_booti(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	unsigned addr = 0;
	char *ptn = "boot";
	int mmcc = -1;
	struct fastboot_boot_img_hdr *hdr = &boothdr;
#ifdef CONFIG_SECURE_BOOT
    u_int32_t load_addr;
    uint32_t image_size;
#endif
	int i = 0;
	bootm_headers_t images;

	for (i = 0; i < argc; i++)
		printf("%s ", argv[i]);
	printf("\n");

	if (argc < 2)
		return -1;

	if (!strncmp(argv[1], "mmc", 3))
		mmcc = simple_strtoul(argv[1]+3, NULL, 10);
	else
		addr = simple_strtoul(argv[1], NULL, 16);

	if (argc > 2)
		ptn = argv[2];

	if (mmcc != -1) {
#ifdef CONFIG_MMC
		struct fastboot_ptentry *pte;
		struct mmc *mmc;
		disk_partition_t info;
		block_dev_desc_t *dev_desc = NULL;
		unsigned sector;

		memset((void *)&info, 0 , sizeof(disk_partition_t));
		/* i.MX use MBR as partition table, so this will have
		   to find the start block and length for the
		   partition name and register the fastboot pte we
		   define the partition number of each partition in
		   config file
		 */
		mmc = find_mmc_device(mmcc);
		if (!mmc) {
			printf("booti: cannot find '%d' mmc device\n", mmcc);
			goto fail;
		}
		dev_desc = get_dev("mmc", mmcc);
		if (NULL == dev_desc) {
			printf("** Block device MMC %d not supported\n", mmcc);
			goto fail;
		}

		/* below was i.MX mmc operation code */
		if (mmc_init(mmc)) {
			printf("mmc%d init failed\n", mmcc);
			goto fail;
		}

		pte = fastboot_flash_find_ptn(ptn);
		if (!pte) {
			printf("booti: cannot find '%s' partition\n", ptn);
			goto fail;
		}

		if (mmc->block_dev.block_read(mmcc, pte->start,
					      1, (void *)hdr) < 0) {
			printf("booti: mmc failed to read bootimg header\n");
			goto fail;
		}

		if (memcmp(hdr->magic, FASTBOOT_BOOT_MAGIC, 8)) {
			printf("booti: bad boot image magic\n");
			goto fail;
		}

		sector = pte->start + (hdr->page_size / 512);

		if (mmc->block_dev.block_read(mmcc, sector,
						(hdr->kernel_size / 512) + 1,
						(void *)hdr->kernel_addr) < 0) {
			printf("booti: mmc failed to read kernel\n");
			goto fail;
		}
		/* flush cache after read */
		flush_cache((ulong)hdr->kernel_addr, hdr->kernel_size); /* FIXME */
		sector += ALIGN_SECTOR(hdr->kernel_size, hdr->page_size) / 512;
		if (mmc->block_dev.block_read(mmcc, sector,
						(hdr->ramdisk_size / 512) + 1,
						(void *)hdr->ramdisk_addr) < 0) {
			printf("booti: mmc failed to read ramdisk\n");
			goto fail;
		}
		/* flush cache after read */
		flush_cache((ulong)hdr->ramdisk_addr, hdr->ramdisk_size); /* FIXME */

#ifdef CONFIG_OF_LIBFDT
		/* load the dtb file */
		if (hdr->second_size && hdr->second_addr) {
			sector += ALIGN_SECTOR(hdr->ramdisk_size, hdr->page_size) / 512;
			if (mmc->block_dev.block_read(mmcc, sector,
						(hdr->second_size / 512) + 1,
						(void *)hdr->second_addr) < 0) {
				printf("booti: mmc failed to dtb\n");
				goto fail;
			}
			/* flush cache after read */
			flush_cache((ulong)hdr->second_addr, hdr->second_size); /* FIXME */
		}
#endif /*CONFIG_OF_LIBFDT*/

#else /*! CONFIG_MMC*/
		return -1;
#endif /*! CONFIG_MMC*/
	} else {
		unsigned kaddr, raddr, end;
#ifdef CONFIG_OF_LIBFDT
		unsigned fdtaddr = 0;
#endif

		/* set this aside somewhere safe */
		memcpy(hdr, (void *) addr, sizeof(*hdr));

		if (memcmp(hdr->magic, FASTBOOT_BOOT_MAGIC, 8)) {
			printf("booti: bad boot image magic\n");
			return 1;
		}

		bootimg_print_image_hdr(hdr);

		kaddr = addr + hdr->page_size;
		raddr = kaddr + ALIGN_SECTOR(hdr->kernel_size, hdr->page_size);
		end = raddr + hdr->ramdisk_size;
#ifdef CONFIG_OF_LIBFDT
		if (hdr->second_size) {
			fdtaddr = raddr + ALIGN_SECTOR(hdr->ramdisk_size, hdr->page_size);
			end = fdtaddr + hdr->second_size;
		}
#endif /*CONFIG_OF_LIBFDT*/
		if (kaddr != hdr->kernel_addr) {
			/*check overlap*/
			if (((hdr->kernel_addr >= addr) &&
					(hdr->kernel_addr <= end)) ||
				((addr >= hdr->kernel_addr) &&
					(addr <= hdr->kernel_addr + hdr->kernel_size))) {
				printf("Fail: booti address overlap with kernel address\n");
				return 1;
			}
			memmove((void *) hdr->kernel_addr,
				(void *)kaddr, hdr->kernel_size);
		}
		if (raddr != hdr->ramdisk_addr) {
			/*check overlap*/
			if (((hdr->ramdisk_addr >= addr) &&
					(hdr->ramdisk_addr <= end)) ||
				((addr >= hdr->ramdisk_addr) &&
					(addr <= hdr->ramdisk_addr + hdr->ramdisk_size))) {
				printf("Fail: booti address overlap with ramdisk address\n");
				return 1;
			}
			memmove((void *) hdr->ramdisk_addr,
				(void *)raddr, hdr->ramdisk_size);
		}

#ifdef CONFIG_OF_LIBFDT
		if (hdr->second_size && fdtaddr != hdr->second_addr) {
			/*check overlap*/
			if (((hdr->second_addr >= addr) &&
					(hdr->second_addr <= end)) ||
				((addr >= hdr->second_addr) &&
					(addr <= hdr->second_addr + hdr->second_size))) {
				printf("Fail: booti address overlap with FDT address\n");
				return 1;
			}
			memmove((void *) hdr->second_addr,
				(void *)fdtaddr, hdr->second_size);
		}
#endif /*CONFIG_OF_LIBFDT*/
	}

	printf("kernel   @ %08x (%d)\n", hdr->kernel_addr, hdr->kernel_size);
	printf("ramdisk  @ %08x (%d)\n", hdr->ramdisk_addr, hdr->ramdisk_size);
#ifdef CONFIG_OF_LIBFDT
	if (hdr->second_size)
		printf("fdt      @ %08x (%d)\n", hdr->second_addr, hdr->second_size);
#endif /*CONFIG_OF_LIBFDT*/

#ifdef CONFIG_SECURE_BOOT
#define IVT_SIZE 0x20
#define CSF_PAD_SIZE 0x2000
	extern uint32_t authenticate_image(uint32_t ddr_start,
					   uint32_t image_size);

	image_size = hdr->ramdisk_addr + hdr->ramdisk_size - hdr->kernel_addr -
		IVT_SIZE - CSF_PAD_SIZE;

	if (authenticate_image(hdr->kernel_addr, image_size)) {
		printf("Authenticate OK\n");
	} else {
		printf("Authenticate image Fail, Please check\n\n");
		return 1;
	}
#endif /*CONFIG_SECURE_BOOT*/

#ifdef CONFIG_CMDLINE_TAG
	char *commandline = getenv("bootargs");

	/* If no bootargs env, just use hdr command line */
	if (!commandline) {
		commandline = (char *)hdr->cmdline;
#ifdef CONFIG_SERIAL_TAG
		char appended_cmd_line[FASTBOOT_BOOT_ARGS_SIZE];
		struct tag_serialnr serialnr;
		get_board_serial(&serialnr);
		if (strlen((char *)hdr->cmdline) +
			strlen("androidboot.serialno") + 17 < FASTBOOT_BOOT_ARGS_SIZE) {
			sprintf(appended_cmd_line,
							"%s androidboot.serialno=%08x%08x",
							(char *)hdr->cmdline,
							serialnr.high,
							serialnr.low);
			commandline = appended_cmd_line;
		} else {
			printf("Cannot append androidboot.serialno\n");
		}

		setenv("bootargs", commandline);
#endif
	}

	/* XXX: in production, you should always use boot.img 's cmdline !!! */
	printf("kernel cmdline:\n");
	printf("\tuse boot.img ");
	printf("command line:\n\t%s\n", commandline);
#endif /*CONFIG_CMDLINE_TAG*/

	memset(&images, 0, sizeof(images));

	/*Setup lmb for memory reserve*/
	boot_start_lmb(&images);

	images.ep = hdr->kernel_addr;
	images.rd_start = hdr->ramdisk_addr;
	images.rd_end = hdr->ramdisk_addr + hdr->ramdisk_size;

	/*Reserve memory for kernel image*/
	lmb_reserve(&images.lmb, images.ep, hdr->kernel_size);

#ifdef CONFIG_OF_LIBFDT
	/*use secondary fields for fdt, second_size= fdt size, second_addr= fdt addr*/
	images.ft_addr = (char *)(hdr->second_addr);
	images.ft_len = (ulong)(hdr->second_size);
	set_working_fdt_addr(images.ft_addr);
#endif /*CONFIG_OF_LIBFDT*/

	arch_preboot_os();

	do_bootm_linux(0, 0, NULL, &images);

	puts("booti: Control returned to monitor - resetting...\n");
	do_reset(cmdtp, flag, argc, argv);
	return 1;

fail:
#ifdef CONFIG_FASTBOOT
	return do_fastboot(NULL, 0, 0, NULL);
#else /*! CONFIG_FASTBOOT*/
	return -1;
#endif /*! CONFIG_FASTBOOT*/
}

U_BOOT_CMD(
	booti,	3,	1,	do_booti,
	"booti   - boot android bootimg from memory\n",
	"[<addr> | mmc0 | mmc1 | mmc2 | mmcX] [<partition>]\n    "
	"- boot application image stored in memory or mmc\n"
	"\t'addr' should be the address of boot image "
	"which is zImage+ramdisk.img\n"
	"\t'mmcX' is the mmc device you store your boot.img, "
	"which will read the boot.img from 1M offset('/boot' partition)\n"
	"\t 'partition' (optional) is the partition id of your device, "
	"if no partition give, will going to 'boot' partition\n"
);
#endif	/* CONFIG_CMD_BOOTI */

#endif	/* CONFIG_FASTBOOT */
