/*
 * Copyright (c) 2012, 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.
 */

#include "cbootimage.h"
#include "data_layout.h"
#include "context.h"
#include "parse.h"
#include "t20/nvboot_bct_t20.h"
#include <string.h>

int enable_debug;
cbootimage_soc_config * g_soc_config;

static void format_u32_hex8(parse_token id, char const * message, void * data);
static void format_u32(parse_token id, char const * message, void * data);
static void format_chipuid(parse_token id, char const * message, void * data);
static void format_hex_16_bytes(parse_token id, char const * message, void * data);
static void format_rsa_param(parse_token id, char const * message, void * data);

typedef void (*format_function)(parse_token id, char const * message, void * data);

typedef struct {
	parse_token id;
	char const * message;
	format_function format;
} value_data;

#define PARAM_TYPE_BINARY_DATA_MAX_SIZE 256
typedef union {
	uint32_t val;
	uint8_t uid[16];
	uint8_t binary[PARAM_TYPE_BINARY_DATA_MAX_SIZE];
} param_types;

#define MAX_PARAM_SIZE sizeof(param_types)

static value_data const values[] = {
	{ token_boot_data_version,   "Version       = ", format_u32_hex8 },
	{ token_block_size,          "BlockSize     = ", format_u32_hex8 },
	{ token_page_size,           "PageSize      = ", format_u32_hex8 },
	{ token_partition_size,      "PartitionSize = ", format_u32_hex8 },
	{ token_odm_data,            "OdmData       = ", format_u32_hex8 },
	{ token_secure_jtag_control, "JtagCtrl      = ", format_u32_hex8 },
	{ token_secure_debug_control, "DebugCtrl     = ", format_u32_hex8 },
	{ token_crypto_hash, 	     "BCT AES Hash  = ", format_hex_16_bytes },
	{ token_rsa_key_modulus,     "RsaKeyModulus:\n", format_rsa_param },
	{ token_rsa_pss_sig_bct,     "RsaPssSigBct:\n", format_rsa_param },
	{ token_unique_chip_id,      "ChipUid       = ", format_chipuid },
	{ token_bootloader_used,     "# Bootloader used       = ", format_u32 },
	{ token_bootloaders_max,     "# Bootloaders max       = ", format_u32 },
	{ token_bct_size,            "# BCT size              = ", format_u32 },
	{ token_hash_size,           "# Hash size             = ", format_u32 },
	{ token_crypto_offset,       "# Crypto offset         = ", format_u32 },
	{ token_crypto_length,       "# Crypto length         = ", format_u32 },
	{ token_max_bct_search_blks, "# Max BCT search blocks = ", format_u32 },
};

static value_data const bl_values[] = {
	{ token_bl_version,     "Version      = ", format_u32_hex8 },
	{ token_bl_start_blk,   "Start block  = ", format_u32 },
	{ token_bl_start_page,  "Start page   = ", format_u32 },
	{ token_bl_length,      "Length       = ", format_u32 },
	{ token_bl_load_addr,   "Load address = ", format_u32_hex8 },
	{ token_bl_entry_point, "Entry point  = ", format_u32_hex8 },
	{ token_bl_attribute,   "Attributes   = ", format_u32_hex8 },
	{ token_bl_crypto_hash, "Bl AES Hash  = ", format_hex_16_bytes },
	{ token_rsa_pss_sig_bl,	"RsaPssSigBl:\n", format_rsa_param },
};

static value_data const mts_values[] = {
	{ token_mts_info_version,      "Version      = ", format_u32_hex8 },
	{ token_mts_info_start_blk,    "Start block  = ", format_u32 },
	{ token_mts_info_start_page,   "Start page   = ", format_u32 },
	{ token_mts_info_length,       "Length       = ", format_u32 },
	{ token_mts_info_load_addr,    "Load address = ", format_u32_hex8 },
	{ token_mts_info_entry_point,  "Entry point  = ", format_u32_hex8 },
	{ token_mts_info_attribute,    "Attributes   = ", format_u32_hex8 },
};

/*****************************************************************************/
static void format_u32_hex8(parse_token id, char const * message, void * data)
{
	printf("%s0x%08x;\n", message, *((uint32_t *) data));
}

static void format_u32(parse_token id, char const * message, void * data)
{
	printf("%s%d;\n", message, *((uint32_t *) data));
}

static void format_chipuid(parse_token id, char const * message, void * data)
{
	uint8_t *uid = (uint8_t *)data;
	int byte_index;
	char uid_str[35] = "0x";
	char *s = &uid_str[2];

	for (byte_index = 15; byte_index >= 0; byte_index--, s += 2)
		sprintf(s, "%02x", uid[byte_index]);

	printf("%s%s;\n", message, uid_str);
}

static void format_hex_16_bytes(parse_token id, char const * message, void * data)
{
	uint8_t *p_byte = (uint8_t *)data;
	int byte_index;

	printf("%s", message);
	for (byte_index = 0; byte_index < 16; ++byte_index)
		printf("%02x", *p_byte++);

	printf(";\n");
}

static void format_rsa_param(parse_token id, char const * message, void * data)
{
#define MAX_BYTE_NUMBER_PER_LINE	16
	uint8_t *rsa = (uint8_t *)data;
	int size, byte_index;

	printf("%s", message);

	if (!g_soc_config->get_value_size)
		return;

	size = g_soc_config->get_value_size(id);
	for (byte_index = 0; byte_index < size; ++byte_index) {
		printf(" %02x", *rsa++);

		if ((byte_index + 1) % MAX_BYTE_NUMBER_PER_LINE == 0)
			printf("\n");
	}

	if (byte_index % MAX_BYTE_NUMBER_PER_LINE != 0)
		printf("\n");
#undef MAX_BYTE_NUMBER_PER_LINE
}

/*****************************************************************************/
static void usage(void)
{
	printf("Usage: bct_dump bctfile\n");
	printf("  bctfile   BCT filename to read and display\n");
}
/*****************************************************************************/
static int max_width(field_item const * table)
{
	int width = 0;
	int i;

	for (i = 0; table[i].name != NULL; ++i) {
		int length = strlen(table[i].name);

		if (width < length)
			width = length;
	}

	return width;
}
/*****************************************************************************/
static enum_item const * find_enum_item(build_image_context *context,
					enum_item const * table,
					uint32_t value)
{
	int i;

	for (i = 0; table[i].name != NULL; ++i) {
		if (table[i].value == value)
			return table + i;
	}

	return NULL;
}
/*****************************************************************************/
static void display_enum_value(build_image_context *context,
			       enum_item const * table,
			       uint32_t value)
{
	enum_item const * e_item = find_enum_item(context, table, value);

	if (e_item)
		printf("%s", e_item->name);
	else
		printf("<UNKNOWN ENUM VALUE (%d)>", value);
}
/*****************************************************************************/
static int display_field_value(build_image_context *context,
			       field_item const * item,
			       uint32_t value)
{
	switch (item->type) {
		case field_type_enum:
			display_enum_value(context, item->enum_table, value);
			break;

		case field_type_u32:
			printf("0x%08x", value);
			break;

		case field_type_u8:
			printf("%d", value);
			break;

		default:
			printf("<UNKNOWN FIELD TYPE (%d)>", item->type);
			return 1;
	}

	return 0;
}
/*****************************************************************************/
int main(int argc, char *argv[])
{
	int e;
	build_image_context context;
	uint32_t bootloaders_used;
	uint32_t parameters_used;
	uint32_t sdram_used;
	uint32_t mts_used;
	nvboot_dev_type type;
	param_types data;
	int i;
	int j;

	if (argc != 2)
		usage();

	memset(&context, 0, sizeof(build_image_context));
	context.bct_filename = argv[1];

	e = read_bct_file(&context);
	if (e != 0)
		return e;

	/* Display root values */
	for (i = 0; i < sizeof(values) / sizeof(values[0]); ++i) {
		if (!g_soc_config->token_supported(values[i].id))
			continue;

		e = g_soc_config->get_value(values[i].id, &data, context.bct);
		if (e)
			memset(&data, 0, MAX_PARAM_SIZE);

		values[i].format(values[i].id, values[i].message, &data);
	}

	/* Display bootloader values */
	e = g_soc_config->get_value(token_bootloader_used,
				     &bootloaders_used,
				     context.bct);

	if ((e == 0) && (bootloaders_used > 0)) {
		int bl_count = sizeof(bl_values) / sizeof(bl_values[0]);

		printf("#\n"
		       "# These values are set by cbootimage using the\n"
		       "# bootloader provided by the Bootloader=...\n"
		       "# configuration option.\n"
		       "#\n");

		for (i = 0; i < bootloaders_used; ++i) {
			for (j = 0; j < bl_count; ++j) {
				e = g_soc_config->getbl_param(i,
							       bl_values[j].id,
							       &data.val,
							       context.bct);
				printf("# Bootloader[%d].", i);

				if (e)
					data.val = -1;

				bl_values[j].format(bl_values[j].id,
							bl_values[j].message,
							&data);
			}
		}
	}

	/* Display mts values */
	e = g_soc_config->get_value(token_mts_used,
				     &mts_used,
				     context.bct);

	if ((e == 0) && (mts_used> 0)) {
		int mts_count = sizeof(mts_values) / sizeof(mts_values[0]);

		printf("#\n"
		       "# These values are set by cbootimage using the\n"
		       "# mts provided by the Mts=... or MtsPreboot=...\n"
		       "# configuration option.\n"
		       "#\n");

		for (i = 0; i < mts_used; ++i) {
			for (j = 0; j < mts_count; ++j) {
				e = g_soc_config->get_mts_info(&context,
								i,
								mts_values[j].id,
								&(data.val));
				printf("# Mts[%d].", i);

				if (e)
					data.val = -1;

				mts_values[j].format(mts_values[j].id,
							mts_values[j].message,
							&data);
			}
		}
	}
	/* Display flash device parameters */
	e = g_soc_config->get_value(token_num_param_sets,
				     &parameters_used,
				     context.bct);

	for (i = 0; (e == 0) && (i < parameters_used); ++i) {
		field_item const * device_field_table = NULL;
		char const * prefix = NULL;
		field_item const * item;

		e = g_soc_config->get_dev_param(&context,
							i,
							token_dev_type,
							&type);
		printf("\n"
		       "DevType[%d] = ", i);
		display_enum_value(&context, g_soc_config->devtype_table, type);
		printf(";\n");

		switch (type) {
			case nvboot_dev_type_spi:
				device_field_table = g_soc_config->spiflash_table;
				prefix = "SpiFlashParams";
				break;

			case nvboot_dev_type_sdmmc:
				device_field_table = g_soc_config->sdmmc_table;
				prefix = "SdmmcParams";
				break;

			case nvboot_dev_type_nand:
				device_field_table = g_soc_config->nand_table;
				prefix = "NandParams";
				break;

			default:
				device_field_table = NULL;
				prefix = "";
				break;
		}

		if (!device_field_table)
			continue;

		int width = max_width(device_field_table);

		for (item = device_field_table; item->name != NULL; ++item) {
			g_soc_config->get_dev_param(&context,
							i,
							item->token,
							&data.val);
			printf("DeviceParam[%d].%s.%-*s = ",
			       i, prefix, width, item->name);

			if (e != 0)
				printf("<ERROR reading parameter (%d)>", e);
			else
				display_field_value(&context, item, data.val);

			printf(";\n");
		}
	}

	/* Display SDRAM parameters */
	e = g_soc_config->get_value(token_num_sdram_sets,
				     &sdram_used,
				     context.bct);

	for (i = 0; (e == 0) && (i < sdram_used); ++i) {
		field_item const *item;

		printf("\n");

		int width = max_width(g_soc_config->sdram_field_table);

		for (item = g_soc_config->sdram_field_table; item->name != NULL; ++item) {
			e = g_soc_config->get_sdram_param(&context,
								i,
								item->token,
								&data.val);
			printf("SDRAM[%d].%-*s = ", i, width, item->name);

			if (e != 0)
				printf("<ERROR reading parameter (%d)>", e);
			else
				display_field_value(&context, item, data.val);

			printf(";\n");
		}
	}

	/* Clean up memory. */
	cleanup_context(&context);

	return e;
}
/*****************************************************************************/
