/*
 * Copyright (c) 2012-2014, NVIDIA CORPORATION.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 */

/*
 * data_layout.c - Code to manage the layout of data in the boot device.
 *
 */

#include "data_layout.h"
#include "cbootimage.h"
#include "crypto.h"
#include "set.h"
#include "context.h"
#include "parse.h"
#include <sys/param.h>

typedef struct blk_data_rec
{
	u_int32_t blk_number;
	u_int32_t pages_used; /* pages always used starting from 0. */
	u_int8_t *data;

	/* Pointer to ECC errors? */

	struct blk_data_rec *next;
} block_data;

/* Function prototypes */
static block_data
*new_block(u_int32_t blk_number, u_int32_t block_size);
static block_data
*find_block(u_int32_t blk_number, block_data  *block_list);
static block_data
*add_block(u_int32_t blk_number, block_data **block_list,
			u_int32_t block_size);
static int
erase_block(build_image_context *context, u_int32_t blk_number);

static int
write_page(build_image_context *context,
			u_int32_t blk_number,
			u_int32_t page_number,
			u_int8_t *data);

static void
insert_padding(u_int8_t *data, u_int32_t length);

static void
write_padding(u_int8_t *data, u_int32_t length);

static int write_bct(build_image_context *context,
		u_int32_t block,
		u_int32_t bct_slot);

static void
set_bl_data(build_image_context *context,
			u_int32_t instance,
			u_int32_t start_blk,
			u_int32_t start_page,
			u_int32_t length);

static int write_image(build_image_context *context, file_type image_type);

static void find_new_bct_blk(build_image_context *context);
static int finish_update(build_image_context *context);

u_int32_t
iceil_log2(u_int32_t a, u_int32_t b)
{
	return (a + (1 << b) - 1) >> b;
}

/* Returns the smallest power of 2 >= a */
u_int32_t
ceil_log2(u_int32_t a)
{
	u_int32_t result;

	result = log2(a);
	if ((1UL << result) < a)
		result++;

	return result;
}

static block_data *new_block(u_int32_t blk_number, u_int32_t block_size)
{
	block_data *new_block = malloc(sizeof(block_data));
	if (new_block == NULL)
		return NULL;

	new_block->blk_number = blk_number;
	new_block->pages_used = 0;
	new_block->data = malloc(block_size);
	if (new_block->data == NULL) {
		free(new_block);
		return NULL;
	}
	new_block->next = NULL;

	memset(new_block->data, 0, block_size);

	return new_block;
}

block_data *new_block_list(void)
{
	return NULL;
}

void destroy_block_list(block_data *block_list)
{
	block_data *next;

	while (block_list) {
		next = block_list->next;
		free(block_list->data);
		free(block_list);
		block_list = next;
	}
}

static block_data *find_block(u_int32_t blk_number, block_data  *block_list)
{
	while (block_list) {
		if (block_list->blk_number == blk_number)
			return block_list;

		block_list = block_list->next;
	}

	return NULL;
}

/* Returns pointer to block after adding it to block_list, if needed. */
static block_data *add_block(u_int32_t blk_number,
		block_data **block_list,
		u_int32_t block_size)
{
	block_data *block = find_block(blk_number,*block_list);
	block_data *parent;

	if (block == NULL) {
		block = new_block(blk_number, block_size);
		if (block == NULL)
			return block;

		/* Insert block into the list */
		if ((*block_list == NULL) ||
			(blk_number < (*block_list)->blk_number)) {
			block->next = *block_list;
			*block_list = block;
		} else {
			/* Search for the correct place to insert the block. */
			parent = *block_list;
			while (parent->next != NULL &&
				parent->next->blk_number < blk_number) {
				parent = parent->next;
			}

			block->next = parent->next;
			parent->next = block;
		}
	}

	return block;
}

static int
erase_block(build_image_context *context, u_int32_t blk_number)
{
	block_data   *block;

	assert(context != NULL);

	block = add_block(blk_number, &(context->memory), context->block_size);

	if (block == NULL)
		return -ENOMEM;
	if (block->data == NULL)
		return -ENOMEM;

	memset(block->data, 0, context->block_size);
	block->pages_used = 0;
	return 0;
}

static int
write_page(build_image_context *context,
	u_int32_t blk_number,
	u_int32_t page_number,
	u_int8_t *data)
{
	block_data *block;
	u_int8_t *page_ptr;

	assert(context);

	block = add_block(blk_number, &(context->memory), context->block_size);

	if (block == NULL)
		return -ENOMEM;
	if (block->data == NULL)
		return -ENOMEM;
	assert(((page_number + 1) * context->page_size)
			<= context->block_size);

	if (block->pages_used != page_number) {
		printf("Warning: Writing page in block out of order.\n");
		printf("     block=%d  page=%d\n", blk_number, page_number);
	}

	page_ptr = block->data + (page_number * context->page_size);
	memcpy(page_ptr, data, context->page_size);
	if (block->pages_used < (page_number+1))
		block->pages_used = page_number+1;
	return 0;
}

static void
insert_padding(u_int8_t *data, u_int32_t length)
{
	u_int32_t aes_blks;
	u_int32_t remaining;

	aes_blks = iceil_log2(length, NVBOOT_AES_BLOCK_SIZE_LOG2);
	remaining = (aes_blks << NVBOOT_AES_BLOCK_SIZE_LOG2) - length;

	write_padding(data + length, remaining);
}

static void
write_padding(u_int8_t *p, u_int32_t remaining)
{
	u_int8_t value = 0x80;

	while (remaining) {
		*p++ = value;
		remaining--;
		value = 0x00;
	}
}

static int
write_bct(build_image_context *context,
	u_int32_t block,
	u_int32_t bct_slot)
{
	u_int32_t pagesremaining;
	u_int32_t page;
	u_int32_t pages_per_bct;
	u_int8_t *buffer;
	u_int8_t *data;
	int err = 0;

	assert(context);

	pages_per_bct = iceil_log2(context->bct_size, context->page_size_log2);
	pagesremaining = pages_per_bct;
	page = bct_slot * pages_per_bct;

	/* Create a local copy of the BCT data */
	buffer = malloc(pages_per_bct * context->page_size);
	if (buffer == NULL)
		return -ENOMEM;
	memset(buffer, 0, pages_per_bct * context->page_size);

	memcpy(buffer, context->bct, context->bct_size);

	insert_padding(buffer, context->bct_size);

	/* Encrypt and compute hash */
	err = sign_bct(context, buffer);
	if (err != 0)
		goto fail;

	/* Write the BCT data to the storage device, picking up ECC errors */
	data = buffer;
	while (pagesremaining > 0) {
		err = write_page(context, block, page, data);
		if (err != 0)
			goto fail;
		page++;
		pagesremaining--;
		data += context->page_size;
	}
fail:
	/* Cleanup */
	free(buffer);
	return err;
}

#define SET_BL_FIELD(instance, field, value)   \
do {                                           \
    g_soc_config->setbl_param(instance,        \
        token_bl_##field,                      \
        &(value),                              \
        context->bct);                         \
} while (0);

#define GET_BL_FIELD(instance, field, ptr)     \
g_soc_config->getbl_param(instance,            \
        token_bl_##field,                      \
        ptr,                                   \
        context->bct);

#define COPY_BL_FIELD(from, to, field)         \
do {                                           \
    u_int32_t v;                               \
    GET_BL_FIELD(from, field, &v);             \
    SET_BL_FIELD(to,   field,  v);             \
} while (0);

#define SET_MTS_FIELD(instance, field, value)   \
do {                                            \
    g_soc_config->set_mts_info(context,         \
    instance,                                   \
    token_mts_info_##field,                     \
    value);                                     \
} while (0);

#define GET_MTS_FIELD(instance, field, ptr)     \
g_soc_config->get_mts_info(context,             \
        instance,                               \
        token_mts_info_##field,                 \
        ptr);

#define COPY_MTS_FIELD(from, to, field)         \
do {                                            \
    u_int32_t v;                                \
    GET_MTS_FIELD(from, field, &v);             \
    SET_MTS_FIELD(to,   field,  v);             \
} while (0);

#define SET_FIELD(is_bl, instance, field, value)\
do {                                            \
    if (is_bl) {                                \
        SET_BL_FIELD(instance, field,value);    \
    }                                           \
    else {                                      \
        SET_MTS_FIELD(instance, field, value);  \
    }                                           \
} while (0);

#define GET_FIELD(is_bl, instance, field, ptr)  \
do {                                            \
    if (is_bl) {                                \
        GET_BL_FIELD(instance, field, ptr);     \
    }                                           \
    else {                                      \
        GET_MTS_FIELD(instance, field, ptr);    \
    }                                           \
} while (0);

#define COPY_FIELD(is_bl, from, to, field)      \
do {                                            \
    if (is_bl) {                                \
        COPY_BL_FIELD(from, to, field);         \
    }                                           \
    else {                                      \
        COPY_MTS_FIELD(from, to, field);        \
    }                                           \
} while (0);

static void
set_bl_data(build_image_context *context,
		u_int32_t instance,
		u_int32_t start_blk,
		u_int32_t start_page,
		u_int32_t length)
{
	assert(context);

	SET_BL_FIELD(instance, version, context->version);
	SET_BL_FIELD(instance, start_blk, start_blk);
	SET_BL_FIELD(instance, start_page, start_page);
	SET_BL_FIELD(instance, length, length);
	SET_BL_FIELD(instance, load_addr, context->newbl_load_addr);
	SET_BL_FIELD(instance, entry_point, context->newbl_entry_point);
	SET_BL_FIELD(instance, attribute, context->newbl_attr);
}

static void
set_mts_data(build_image_context *context,
		u_int32_t instance,
		u_int32_t start_blk,
		u_int32_t start_page,
		u_int32_t length)
{
	assert(context);

	SET_MTS_FIELD(instance, version, context->version);
	SET_MTS_FIELD(instance, start_blk, start_blk);
	SET_MTS_FIELD(instance, start_page, start_page);
	SET_MTS_FIELD(instance, length, length);
	SET_MTS_FIELD(instance, load_addr, context->mts_load_addr);
	SET_MTS_FIELD(instance, entry_point, context->mts_entry_point);
	SET_MTS_FIELD(instance, attribute, context->mts_attr);
}

#define SET_DATA(is_bl, context, instance, start_blk, start_page, length) \
do {                                                                      \
    if (is_bl)                                                            \
        set_bl_data(context, instance, start_blk, start_page, length);    \
    else                                                                  \
        set_mts_data(context, instance, start_blk, start_page, length);   \
} while (0);

/*
 * Load the image then update it with the information from config file.
 * In the interest of expediency, all image's allocated from bottom to top
 * start at page 0 of a block, and all image's allocated from top to bottom
 * end at the end of a block.
 *
 * @param context      The main context pointer
 * @param image_type   The image type. Can be file_type_bl or file_type_mts
 *                     only right now
 * @return 0 for success
 */
static int
write_image(build_image_context *context, file_type image_type)
{
	u_int32_t i, j;
	u_int32_t image_instance;
	u_int32_t image_move_count = 0;
	u_int32_t image_move_remaining;
	u_int32_t current_blk;
	u_int32_t current_page;
	u_int32_t pages_in_image;
	u_int32_t image_used;
	u_int8_t  *image_storage; /* Holds the image after reading */
	u_int8_t  *buffer;	/* Holds the image for writing */
	u_int8_t  *src;	/* Scans through the image during writing */
	u_int32_t  image_actual_size; /* In bytes */
	u_int32_t  pagesremaining;
	u_int32_t  virtual_blk;
	u_int32_t  pages_per_blk;
	u_int32_t  image_version;
	u_int8_t  *hash_buffer;
	u_int32_t  hash_size;
	u_int32_t  image_max;
	parse_token token;
	int err = 0, is_bl;

	assert(context);

	/* Only support bootloader and mts image right now */
	if (image_type == file_type_bl) {
		is_bl = 1;
	}
	else if (image_type == file_type_mts) {
		is_bl = 0;
	}
	else {
		printf("Not supported image type!\n");
		return -EINVAL;
	}

	pages_per_blk = 1 << (context->block_size_log2
			- context->page_size_log2);

	g_soc_config->get_value(token_hash_size,
			&hash_size, context->bct);
	token = is_bl ? token_bootloaders_max : token_mts_max;
	g_soc_config->get_value(token, &image_max, context->bct);

	hash_buffer = calloc(1, hash_size);
	if (hash_buffer == NULL)
		return -ENOMEM;

	if (enable_debug) {
		printf("writing %s\n", is_bl ? "bootloader" : "mts image");
		printf("  redundancy = %d\n", context->redundancy);
	}

	/* Make room for the image in the BCT. */

	/* Determine how many to move.
	 * Note that this code will count Mts[0] only if there is already
	 * a mts in the device.
	 */
	GET_FIELD(is_bl, 0, version, &image_version);
	token = is_bl ? token_bootloader_used : token_mts_used;
	g_soc_config->get_value(token, &image_used, context->bct);
	for (image_instance = 0; image_instance < image_used; image_instance++) {
		u_int32_t tmp;
		GET_FIELD(is_bl, image_instance, version, &tmp);
		if (tmp == image_version)
			image_move_count++;
	}

	/* Adjust the move count, if needed, to avoid overflowing the mts table.
	 * This can happen due to too much redundancy.
	 */
	image_move_count = MIN(image_move_count, image_max - context->redundancy);

	/* Move the mts entries down. */
	image_move_remaining = image_move_count;
	while (image_move_remaining > 0) {
		u_int32_t  inst_from = image_move_remaining - 1;
		u_int32_t  inst_to   =
			image_move_remaining + context->redundancy - 1;

		COPY_FIELD(is_bl, inst_from, inst_to, version);
		COPY_FIELD(is_bl, inst_from, inst_to, start_blk);
		COPY_FIELD(is_bl, inst_from, inst_to, start_page);
		COPY_FIELD(is_bl, inst_from, inst_to, length);
		COPY_FIELD(is_bl, inst_from, inst_to, load_addr);
		COPY_FIELD(is_bl, inst_from, inst_to, entry_point);
		COPY_FIELD(is_bl, inst_from, inst_to, attribute);

		if (is_bl) {
			g_soc_config->getbl_param(inst_from,
				token_bl_crypto_hash,
				(u_int32_t*)hash_buffer,
				context->bct);
			g_soc_config->setbl_param(inst_to,
				token_bl_crypto_hash,
				(u_int32_t*)hash_buffer,
				context->bct);
		}

		image_move_remaining--;
	}

	/* Read the image into memory. */
	if (read_from_image(
		is_bl ? context->newbl_filename : context->mts_filename,
		0,
		is_bl ? MAX_BOOTLOADER_SIZE : MAX_MTS_SIZE,
		&image_storage,
		&image_actual_size,
		image_type) == 1) {
		printf("Error reading image %s.\n",
			is_bl ? context->newbl_filename : context->mts_filename);
		exit(1);
	}

	pages_in_image = iceil_log2(image_actual_size, context->page_size_log2);

	current_blk = context->next_bct_blk;
	current_page  = 0;
	for (image_instance = 0; image_instance < context->redundancy;
					image_instance++) {

		pagesremaining = pages_in_image;
		/* Advance to the next block if needed. */
		if (current_page > 0) {
			current_blk++;
			current_page = 0;
		}

		virtual_blk = 0;

		while (pagesremaining > 0) {
			/* Update the bad block table with relative
			  * bad blocks.
			  */
			if (virtual_blk == 0) {
				SET_DATA(is_bl,
					context,
					image_instance,
					current_blk,
					current_page,
					image_actual_size);
			}

			if (pagesremaining > pages_per_blk) {
				current_blk++;
				virtual_blk++;
				pagesremaining -= pages_per_blk;
			} else {
				current_page = pagesremaining;
				pagesremaining = 0;
			}
		}
	}

	/* Scan forwards to write each copy. */
	for (image_instance = 0; image_instance < context->redundancy;
					image_instance++) {

		/* Create a local copy of the BCT data */
		buffer = malloc(pages_in_image * context->page_size);
		if (buffer == NULL)
			return -ENOMEM;

		memset(buffer, 0, pages_in_image * context->page_size);
		memcpy(buffer, image_storage, image_actual_size);

		insert_padding(buffer, image_actual_size);

		pagesremaining = pages_in_image;

		if (is_bl) {
			GET_BL_FIELD(image_instance, start_blk, &current_blk);
			GET_BL_FIELD(image_instance, start_page,  &current_page);

			/* Encrypt and compute hash */
			sign_data_block(buffer,
					image_actual_size,
					hash_buffer);
			g_soc_config->setbl_param(image_instance,
					token_bl_crypto_hash,
					(u_int32_t*)hash_buffer,
					context->bct);
		}

		GET_FIELD(is_bl, image_instance, start_blk, &current_blk);
		GET_FIELD(is_bl, image_instance, start_page,  &current_page);

		/* Write the BCT data to the storage device,
		 * picking up ECC errors
		 */
		src = buffer;

		/* Write pages as we go. */
		virtual_blk = 0;
		while (pagesremaining) {
			if (current_page == 0) {
				/* Erase the block before writing into it. */
				erase_block(context, current_blk);
			}

			err = write_page(context,
				current_blk, current_page, src);
			if (err != 0)
				goto fail;
			pagesremaining--;
			src += context->page_size;
			current_page++;
			if (current_page >= pages_per_blk) {
				current_page = 0;
				current_blk++;
				virtual_blk++;
			}
			context->last_blk = current_blk;
		}
		context->next_bct_blk = context->last_blk + 1;
		free(buffer);
	}

	image_used = context->redundancy + image_move_count;
	token = is_bl ? token_bootloader_used : token_mts_used;
	g_soc_config->set_value(token, &image_used, context->bct);

	if (enable_debug) {
		for (i = 0; i < image_max; i++) {
			u_int32_t version;
			u_int32_t start_blk;
			u_int32_t start_page;
			u_int32_t length;
			u_int32_t load_addr;
			u_int32_t entry_point;

			GET_FIELD(is_bl, i, version,     &version);
			GET_FIELD(is_bl, i, start_blk,  &start_blk);
			GET_FIELD(is_bl, i, start_page,   &start_page);
			GET_FIELD(is_bl, i, length,      &length);
			GET_FIELD(is_bl, i, load_addr, &load_addr);
			GET_FIELD(is_bl, i, entry_point,  &entry_point);

			printf("%s%s[%d]: %d %04d %04d %04d 0x%08x 0x%08x\n",
				i < image_used ? "  " : "**",
				is_bl ? "BL" : "MTS",
				i,
				version,
				start_blk,
				start_page,
				length,
				load_addr,
				entry_point);
			if (is_bl) {
				g_soc_config->getbl_param(i,
					token_bl_crypto_hash,
					(u_int32_t*)hash_buffer,
					context->bct);
				for (j = 0; j < hash_size / 4; j++) {
					printf("%08x",
						*((u_int32_t*)(hash_buffer + 4*j)));
				}
				printf("\n");
			}
		}
	}
	free(image_storage);
	free(hash_buffer);
	return 0;

fail:
	/* Cleanup. */
	free(buffer);
	free(image_storage);
	free(hash_buffer);
	printf("Write image failed, error: %d.\n", err);
	return err;
}

void
update_context(struct build_image_context_rec *context)
{
	g_soc_config->get_value(token_partition_size,
			&context->partition_size,
			context->bct);
	g_soc_config->get_value(token_page_size_log2,
			&context->page_size_log2,
			context->bct);
	g_soc_config->get_value(token_block_size_log2,
			&context->block_size_log2,
			context->bct);
	g_soc_config->get_value(token_odm_data,
			&context->odm_data,
			context->bct);

	context->page_size = 1 << context->page_size_log2;
	context->block_size = 1 << context->block_size_log2;
	context->pages_per_blk = 1 << (context->block_size_log2 -
                                           context->page_size_log2);
}

/*
 * Allocate and initialize the memory for bct data.
 *
 * @param context		The main context pointer
 * @return 0 for success
 */
int
init_bct(struct build_image_context_rec *context)
{
	/* Allocate space for the bct.	 */
	context->bct = malloc(context->bct_size);

	if (context->bct == NULL)
		return -ENOMEM;

	memset(context->bct, 0, context->bct_size);
	context->bct_init = 1;

	return 0;
}

/*
 * Read the bct data from given file to allocated memory.
 * Assign the global parse interface to corresponding hardware interface
 *   according to the boot data version in bct file.
 *
 * @param context		The main context pointer
 * @return 0 for success
 */
int
read_bct_file(struct build_image_context_rec *context)
{
	u_int8_t  *bct_storage; /* Holds the Bl after reading */
	u_int32_t  bct_actual_size; /* In bytes */
	file_type bct_filetype = file_type_bct;
	int err = 0;

	if (read_from_image(context->bct_filename,
			0,
			NVBOOT_CONFIG_TABLE_SIZE_MAX,
			&bct_storage,
			&bct_actual_size,
			bct_filetype) == 1) {
		printf("Error reading bct file %s.\n", context->bct_filename);
		exit(1);
	}

	context->bct_size = bct_actual_size;
	if (context->bct_init != 1)
		err = init_bct(context);
	if (err != 0) {
		printf("Context initialization failed.  Aborting.\n");
		return err;
	}
	memcpy(context->bct, bct_storage, context->bct_size);
	free(bct_storage);

	if (!data_is_valid_bct(context))
		return ENODATA;

	return err;
}

/*
 * Update the next_bct_blk and make it point to the next
 * new blank block according to bct_copy given.
 *
 * @param context		The main context pointer
 */
static void
find_new_bct_blk(build_image_context *context)
{
	u_int32_t max_bct_search_blks;

	assert(context);

	g_soc_config->get_value(token_max_bct_search_blks,
			&max_bct_search_blks, context->bct);

	if (context->next_bct_blk > max_bct_search_blks) {
		printf("Error: Unable to locate a journal block.\n");
		exit(1);
	}
	context->next_bct_blk++;
}

/*
 * Initialization before bct and bootloader update.
 * Find the new blank block and erase it.
 *
 * @param context		The main context pointer
 * @return 0 for success
 */
int
begin_update(build_image_context *context)
{
	u_int32_t hash_size;
	u_int32_t reserved_size;
	u_int32_t reserved_offset;
	int err = 0;
	int i;

	assert(context);

	/* Ensure that the BCT block & page data is current. */
	if (enable_debug) {
		u_int32_t block_size_log2;
		u_int32_t page_size_log2;

		g_soc_config->get_value(token_block_size_log2,
			&block_size_log2, context->bct);
		g_soc_config->get_value(token_page_size_log2,
			&page_size_log2, context->bct);

		printf("begin_update(): bct data: b=%d p=%d\n",
			block_size_log2, page_size_log2);
	}

	g_soc_config->set_value(token_boot_data_version,
			&(context->boot_data_version), context->bct);
	g_soc_config->get_value(token_hash_size,
			&hash_size, context->bct);
	g_soc_config->get_value(token_reserved_size,
			&reserved_size, context->bct);
	g_soc_config->get_value(token_reserved_offset,
			&reserved_offset, context->bct);
	/* Set the odm data */
	g_soc_config->set_value(token_odm_data,
			&(context->odm_data), context->bct);

	/* Initialize the bad block table field. */
	g_soc_config->init_bad_block_table(context);
	/* Fill the reserved data w/the padding pattern. */
	write_padding(context->bct + reserved_offset, reserved_size);

	/* Create the pad before the BCT starting at block 1 */
	for (i = 0; i < context->pre_bct_pad_blocks; i++) {
		find_new_bct_blk(context);
		err = erase_block(context, i);
		if (err != 0)
			goto fail;
	}
	/* Find the next bct block starting at block pre_bct_pad_blocks. */
	for (i = 0; i < context->bct_copy; i++) {
		find_new_bct_blk(context);
		err = erase_block(context, i + context->pre_bct_pad_blocks);
		if (err != 0)
			goto fail;
	}
	return 0;
fail:
	printf("Erase block failed, error: %d.\n", err);
	return err;
}

/*
 * Write the BCT(s) starting at slot 0 of block context->pre_bct_pad_blocks.
 *
 * @param context		The main context pointer
 * @return 0 for success
 */
static int
finish_update(build_image_context *context)
{
	int err = 0;
	int i;

	for (i = 0; i < context->bct_copy; i++) {
		err = write_bct(context, i + context->pre_bct_pad_blocks, 0);
		if (err != 0)
			goto fail;
	}

	return 0;
fail:
	printf("Write BCT failed, error: %d.\n", err);
	return err;
}

/*
 * For now, ignore end state.
 */
int
update_bl(build_image_context *context)
{
	if (enable_debug)
		printf("**update_bl()\n");

	if (begin_update(context) != 0)
		return 1;
	if (write_image(context, file_type_bl) != 0)
		return 1;
	if (finish_update(context) != 0)
		return 1;
	return 0;
}

int
update_mts_image(build_image_context *context)
{
	if (enable_debug)
		printf("**update_mts()\n");

	if (begin_update(context) != 0)
		return 1;
	if (write_image(context, file_type_mts) != 0)
		return 1;
	if (finish_update(context) != 0)
		return 1;
	return 0;
}

/*
 * To write the current image:
 *   Loop over all blocks in the block data list:
 *     Write out the data of real blocks.
 *     Write out 0's for unused blocks.
 *     Stop on the last used page of the last used block.
 *
 * @param context		The main context pointer
 * @return 0 for success
 */
int
write_block_raw(build_image_context *context)
{
	block_data *block_list;
	block_data *block;
	u_int32_t blk_number;
	u_int32_t last_blk;
	u_int32_t pages_to_write;
	u_int8_t *data;
	u_int8_t *empty_blk = NULL;

	assert(context != NULL);
	assert(context->memory);

	block_list = context->memory;

	/* Compute the end of the image. */
	block_list = context->memory;
	while (block_list->next)
		block_list = block_list->next;

	last_blk = block_list->blk_number;

	/* Loop over all the storage from block 0, page 0 to
	 *last_blk, Lastpage
	 */
	for (blk_number = 0; blk_number <= last_blk; blk_number++) {
		block = find_block(blk_number, context->memory);
		if (block)	{
			pages_to_write = (blk_number == last_blk) ?
			block->pages_used :
			context->pages_per_blk;
			data = block->data;
		} else {
			/* Allocate empty_blk if needed. */
			if (empty_blk == NULL) {
				empty_blk = malloc(context->block_size);
				if (!empty_blk)
					return -ENOMEM;
				memset(empty_blk, 0, context->block_size);
			}
			pages_to_write = context->pages_per_blk;
			data = empty_blk;
		}
		/* Write the data */
		{
			size_t bytes = pages_to_write * context->page_size;

			if (fwrite(data, 1, bytes, context->raw_file) != bytes)
				return -1;
		}
	}

	free(empty_blk);
	return 0;
}

int write_data_block(FILE *fp, u_int32_t offset, u_int32_t size, u_int8_t *buffer)
{
	if (fseek(fp, offset, 0))
		return -1;

	return fwrite(buffer, 1, size, fp);
}

int data_is_valid_bct(build_image_context *context)
{
	/* get proper soc_config pointer by polling each supported chip */
	if (if_bct_is_t20_get_soc_config(context, &g_soc_config))
		return 1;
	if (if_bct_is_t30_get_soc_config(context, &g_soc_config))
		return 1;
	if (if_bct_is_t114_get_soc_config(context, &g_soc_config))
		return 1;
	if (if_bct_is_t124_get_soc_config(context, &g_soc_config))
		return 1;
	if (if_bct_is_t132_get_soc_config(context, &g_soc_config))
		return 1;

	return 0;
}

int get_bct_size_from_image(build_image_context *context)
{
	u_int8_t buffer[NVBOOT_CONFIG_TABLE_SIZE_MAX];
	u_int32_t bct_size = 0;
	FILE *fp;

	fp = fopen(context->input_image_filename, "r");
	if (!fp)
		return ENODATA;

	if (!fread(buffer, 1, NVBOOT_CONFIG_TABLE_SIZE_MAX, fp)) {
		fclose(fp);
		return ENODATA;
	}

	context->bct = buffer;
	if (data_is_valid_bct(context) && g_soc_config->get_bct_size)
		bct_size = g_soc_config->get_bct_size();

	fclose(fp);
	context->bct = 0;
	return bct_size;
}
