/*
 * 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.
 */

/*
 * set.c - State setting support for the cbootimage tool
 */

#include "set.h"
#include "cbootimage.h"
#include "crypto.h"
#include "data_layout.h"

/*
 * Function prototypes
 *
 * ParseXXX() parses XXX in the input
 * SetXXX() sets state based on the parsing results but does not perform
 *      any parsing of its own
 * A ParseXXX() function may call other parse functions and set functions.
 * A SetXXX() function may not call any parseing functions.
 */
#define DEFAULT()                                                     \
	default:                                                      \
		printf("Unexpected token %d at line %d\n",            \
		token, __LINE__);                              \
	return 1

int
read_from_image(char	*filename,
		uint32_t	offset,
		uint32_t	max_size,
		uint8_t	**image,
		uint32_t	*actual_size,
		file_type	f_type)
{
	int result = 0; /* 0 = success, 1 = failure */
	FILE *fp;
	struct stat stats;

	fp = fopen(filename, "rb");
	if (fp == NULL) {
		result = 1;
		return result;
	}

	if (fseek(fp, offset, SEEK_SET) == -1) {
		printf("Error: Couldn't seek to %s(%d)\n", filename, offset);
		result = 1;
		goto cleanup;
	}

	if (stat(filename, &stats) != 0) {
		printf("Error: Unable to query info on bootloader path %s\n",
			filename);
		result = 1;
		goto cleanup;
	}

	if (f_type == file_type_bl) {
		if ((stats.st_size - offset) > max_size) {
			printf("Error: Bootloader file %s is too large.\n",
				filename);
			result = 1;
			goto cleanup;
		}
		*actual_size = (uint32_t)stats.st_size;
	} else {
		if ((stats.st_size - offset) < max_size)
			*actual_size = stats.st_size - offset;
		else
			*actual_size = max_size;
	}

	*image = malloc(*actual_size);
	if (*image == NULL) {
		result = 1;
		goto cleanup;
	}

	memset(*image, 0, *actual_size);

	if (fread(*image, 1, (size_t)(*actual_size), fp) !=
		(size_t)(*actual_size)) {
		result = 1;
		goto cleanup;
	}

cleanup:
	fclose(fp);
	return result;
}

/*
 * Processes commands to set a bootloader.
 *
 * @param context    	The main context pointer
 * @param filename   	The file name of bootloader
 * @param load_addr  	The load address value for bootloader
 * @param entry_point	The entry point value for bootloader
 * @return 0 and 1 for success and failure
 */
int
set_bootloader(build_image_context	*context,
		char	*filename,
		uint32_t	load_addr,
		uint32_t	entry_point)
{
	context->newbl_filename = filename;
	context->newbl_load_addr = load_addr;
	context->newbl_entry_point = entry_point;
	return update_bl(context);
}

/*
 * Processes commands to set a MTS image.
 *
 * @param context    	The main context pointer
 * @param filename   	The file name of MTS image
 * @param load_addr  	The load address value for MTS image
 * @param entry_point	The entry point value for MTS image
 * @return 0 and 1 for success and failure
 */
int
set_mts_image(build_image_context	*context,
		char	*filename,
		uint32_t	load_addr,
		uint32_t	entry_point)
{
	context->mts_filename = filename;
	context->mts_load_addr = load_addr;
	context->mts_entry_point = entry_point;
	return update_mts_image(context);
}

int
set_rsa_param(build_image_context *context, parse_token token,
		char *filename)
{
	int	result;
	uint8_t *rsa_storage;	/* Holds the rsa param after reading */
	int32_t size;		/* Bytes to read */
	uint32_t actual_size;	/* In bytes */

	if ((size = g_soc_config->get_value_size(token)) <= 0)  {
		printf("Error: Unsupported token %d for value size.\n", token);
		exit(1);
	}

	/* Read the image into memory. */
	result = read_from_image(filename,
				0,
				(uint32_t)size,
				&rsa_storage,
				&actual_size,
				file_type_bin);

	if (result) {
		printf("Error reading file %s.\n", filename);
		exit(1);
	}

	if (actual_size != size) {
		printf("Error: invalid size, file %s.\n", filename);
		exit(1);
	}

	if (enable_debug)
		printf("Updating token %d with file %s\n", (int)token, filename);

	/* set to appropriate bct field */
	result = g_soc_config->set_value(token,
			rsa_storage, context->bct);

	free(rsa_storage);
	return result;
}

#define DEFAULT()                                                     \
	default:                                                      \
		printf("Unexpected token %d at line %d\n",            \
			token, __LINE__);                              \
		return 1

/*
 * General handler for setting values in config files.
 *
 * @param context	The main context pointer
 * @param token  	The parse token value
 * @param value  	The pointer of value to set
 * @return 0 for success
 */
int context_set_value(build_image_context *context,
		parse_token token,
		void *value)
{
	assert(context != NULL);

	switch (token) {
	case token_attribute:
		context->newbl_attr = *((uint32_t *)value);
		break;

	case token_block_size:
		context->block_size = *((uint32_t *)value);
		context->block_size_log2 = log2(*((uint32_t *)value));

		if (context->memory != NULL) {
			printf("Error: Too late to change block size.\n");
			return 1;
		}

		if (context->block_size != (uint32_t)(1 << context->block_size_log2)) {
			printf("Error: Block size must be a power of 2.\n");
			return 1;
		}
		context->pages_per_blk= 1 << (context->block_size_log2- 
				context->page_size_log2);
		g_soc_config->set_value(token_block_size_log2,
			&(context->block_size_log2), context->bct);
		break;

	case token_partition_size:
		if (context->memory != NULL) {
			printf("Error: Too late to change block size.\n");
			return 1;
		}

		context->partition_size= *((uint32_t *)value);
		g_soc_config->set_value(token_partition_size,
			value, context->bct);
		break;

	case token_page_size:
		context->page_size = *((uint32_t *)value);
		context->page_size_log2 = log2(*((uint32_t *)value));

		if (context->page_size != (uint32_t)(1 << context->page_size_log2)) {
			printf("Error: Page size must be a power of 2.\n");
			return 1;
		}
		context->pages_per_blk= 1 << (context->block_size_log2- 
			context->page_size_log2);

		g_soc_config->set_value(token_page_size_log2,
			&(context->page_size_log2), context->bct);
		break;
	case token_redundancy:
		context->redundancy = *((uint32_t *)value);
		break;

	case token_version:
		context->version = *((uint32_t *)value);
		break;

	case token_bct_copy:
		context->bct_copy = *((uint32_t *)value);
		break;

	case token_odm_data:
		context->odm_data = *((uint32_t *)value);
		break;

	case token_pre_bct_pad_blocks:
		if (context->bct_init) {
			printf("Error: Too late to pre-BCT pad.\n");
			return 1;
		}
		context->pre_bct_pad_blocks = *((uint32_t *)value);
		break;

	case token_secure_jtag_control:
		context->secure_jtag_control = *((uint32_t *)value);
		g_soc_config->set_value(token_secure_jtag_control,
			value, context->bct);
		break;

	case token_secure_debug_control:
		context->secure_debug_control = *((uint32_t *)value);
		g_soc_config->set_value(token_secure_debug_control,
			value, context->bct);
		break;

	case token_unique_chip_id:
		memcpy(context->unique_chip_id, value, 16);
		g_soc_config->set_value(token_unique_chip_id,
			value, context->bct);
		break;

	DEFAULT();
	}

	return 0;
}
