/*
 * Copyright 2018 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/* This file implements the UART console application operations. */
#define _GNU_SOURCE
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "com_port.h"
#include "cmd.h"
#include "main.h"
#include "misc_util.h"
#include "opr.h"

/*----------------------------------------------------------------------------
 * Global Variables
 *---------------------------------------------------------------------------
 */
int port_handle = INVALID_HANDLE_VALUE;

/*----------------------------------------------------------------------------
 * Constant definitions
 *---------------------------------------------------------------------------
 */
#define MAX_PORT_NAME_SIZE 32
#define OPR_TIMEOUT 10L		 /* 10  seconds */
#define FLASH_ERASE_TIMEOUT 120L /* 120 seconds */

#define STS_MSG_MIN_SIZE 8
#define STS_MSG_APP_END 0x09
#define DUMMY_SIZE 2

#define MAX_SYNC_TRIALS 3

/*----------------------------------------------------------------------------
 * Global variables
 *---------------------------------------------------------------------------
 */
struct command_node cmd_buf[MAX_CMD_BUF_SIZE];
uint8_t resp_buf[MAX_RESP_BUF_SIZE];

/*---------------------------------------------------------------------------
 * Functions prototypes
 *---------------------------------------------------------------------------
 */
static bool opr_send_cmds(struct command_node *cmd_buf, uint32_t cmd_num);

/*----------------------------------------------------------------------------
 * Function implementation
 *----------------------------------------------------------------------------
 */

/*----------------------------------------------------------------------------
 * Function:	opr_usage
 *
 * Parameters:	none.
 * Returns:	none.
 * Side effects:
 * Description:
 *		Prints the console application operation menu.
 *---------------------------------------------------------------------------
 */
void opr_usage(void)
{
	printf("Operations:\n");
	printf("       %s\t\t- Write To Memory/Flash\n", OPR_WRITE_MEM);
	printf("       %s\t\t- Read From Memory/Flash\n", OPR_READ_MEM);
	printf("       %s\t\t- Execute a non-return code\n", OPR_EXECUTE_EXIT);
	printf("       %s\t\t- Execute a returnable code\n", OPR_EXECUTE_CONT);
}

/*----------------------------------------------------------------------------
 * Function:	opr_close_port
 *
 * Parameters:	none
 * Returns:
 * Side effects:
 * Description:
 *		This routine closes the opened COM port by the application
 *---------------------------------------------------------------------------
 */
bool opr_close_port(void)
{
	return com_port_close(port_handle);
}

/*----------------------------------------------------------------------------
 * Function:        opr_open_port
 *
 * Parameters:	port_name - COM Port name.
 *		port_cfg - COM Port configuration structure.
 * Returns:	1 if successful, 0 in the case of an error.
 * Side effects:
 * Description:
 *		Open a specified ComPort device.
 *---------------------------------------------------------------------------
 */
bool opr_open_port(const char *port_name, struct comport_fields port_cfg)
{
	char *full_port_name;

	if (asprintf(&full_port_name, "/dev/%s", port_name) == -1)
		return false;

	if (port_handle > 0)
		com_port_close(port_handle);

	port_handle = com_port_open((const char *)full_port_name, port_cfg);

	if (port_handle <= 0) {
		display_color_msg(FAIL, "\nERROR: COM Port failed to open.\n");
		DISPLAY_MSG(
			("Please select the right serial port or check if "
			 "other serial\n"));
		DISPLAY_MSG(("communication applications are opened.\n"));
		return false;
	}

	display_color_msg(SUCCESS, "Port %s Opened\n", full_port_name);

	return true;
}

/*----------------------------------------------------------------------------
 * Function:	opr_write_chunk
 *
 * Parameters:
 *      buffer  - Input data buffer.
 *      addr    - Memory address to write to.
 *      size    - Data size to write.
 * Returns: true if successful, false in the case of an error.
 * Side effects:
 * Description:
 *   Write data to RAM, starting from the given  address (addr).
 *   Data size is limited to the max chunk size (256 bytes).
 *---------------------------------------------------------------------------
 */
bool opr_write_chunk(uint8_t *buffer, uint32_t addr, uint32_t size)
{
	struct command_node wr_cmd_buf;

	if (size > MAX_RW_DATA_SIZE) {
		display_color_msg(FAIL,
			"ERROR: Block cannot exceed %d\n", MAX_RW_DATA_SIZE);
	}
	/* Initialize response size */
	wr_cmd_buf.resp_size = 1;
	cmd_create_write(addr, size, buffer,
			wr_cmd_buf.cmd, &wr_cmd_buf.cmd_size);
	return opr_send_cmds(&wr_cmd_buf, 1);
}

/*----------------------------------------------------------------------------
 * Function:	opr_read_chunk
 *
 * Parameters:
 *      buffer  - Data read buffer.
 *      addr    - Memory address to read from.
 *      size    - Data size to read.
 * Returns: true if successful, false in the case of an error.
 * Side effects:
 * Description:
 *   Read data from RAM, starting from the given  address (addr).
 *   Data size is limited to the max chunk size (256 bytes).
 *---------------------------------------------------------------------------
 */
bool opr_read_chunk(uint8_t *buffer, uint32_t addr, uint32_t size)
{
	struct command_node rd_cmd_buf;

	if (size > MAX_RW_DATA_SIZE) {
		display_color_msg(FAIL,
			"ERROR: Block cannot exceed %d\n", MAX_RW_DATA_SIZE);
		return false;
	}

	cmd_create_read(addr, ((uint8_t)size - 1),
					rd_cmd_buf.cmd, &rd_cmd_buf.cmd_size);
	rd_cmd_buf.resp_size = size + 3;
	if (opr_send_cmds(&rd_cmd_buf, 1)) {
		if (resp_buf[0] == (uint8_t)(UFPP_READ_CMD)) {
			memcpy(buffer, &resp_buf[1], size);
			return true;
		}
	}
	return false;
}
/*----------------------------------------------------------------------------
 * Function:	opr_write_mem
 *
 * Parameters:	input	- Input (file-name/console), containing data to write.
 *		addr	- Memory address to write to.
 *		size	- Data size to write.
 * Returns:	none.
 * Side effects:
 * Description:
 *	Write data to memory, starting from a given address.
 *	Memory may be Flash (SPI), DRAM (DDR) or SRAM.
 *	The data is retrieved either from an input file or from a console.
 *	Data size is not limited.
 *	Data is sent in 256 bytes chunks (file mode) or 4 bytes chunks
 *	(console mode).
 *---------------------------------------------------------------------------
 */
void opr_write_mem(uint8_t *buffer, uint32_t addr, uint32_t size)
{
	uint32_t cur_addr = addr;
	uint8_t data_buf[256];
	uint32_t write_size, size_remain;
	uint32_t cmd_idx = 1;
	char seps[] = " ";
	char *token = NULL;
	char *stop_str;
	uint32_t block_size = (console) ? sizeof(uint32_t) : MAX_RW_DATA_SIZE;
	struct command_node wr_cmd_buf;

	/* Initialize response size */
	wr_cmd_buf.resp_size = 1;

	DISPLAY_MSG(("Writing [%d] bytes in [%d] packets\n", size,
		((size + (block_size - 1)) / block_size)));

	/* Read first token from string */
	if (console)
		token = strtok(buffer, seps);

	size_remain = size;
	/* Main write loop */
	while (true) {
		if (console) {
			/*
			 * Invert token to double-word and insert the value to
			 * data buffer
			 */
			(*(uint32_t *)data_buf) =
				strtoul(token, &stop_str, BASE_HEXADECIMAL);

			/* Prepare the next iteration */
			token = strtok(NULL, seps);
		}
		write_size = (size_remain > block_size) ?
					block_size : size_remain;
		if (console) {
			cmd_create_write(cur_addr, write_size, data_buf,
					wr_cmd_buf.cmd, &wr_cmd_buf.cmd_size);
		} else {
			cmd_create_write(cur_addr, write_size, buffer,
					wr_cmd_buf.cmd, &wr_cmd_buf.cmd_size);
			buffer += write_size;
		}
		if (opr_send_cmds(&wr_cmd_buf, 1) != true)
			break;

		cmd_disp_write(resp_buf, write_size, cmd_idx,
			((size + (block_size - 1)) / block_size));
		cur_addr += write_size;
		size_remain -= write_size;
		cmd_idx++;
		if (size_remain == 0)
			break;
	}

	DISPLAY_MSG(("\n"));
}

/*----------------------------------------------------------------------------
 * Function:	opr_read_mem
 *
 * Parameters:	output - Output file name, containing data that was read.
 *		addr   - Memory address to read from.
 *		size   - Data size to read.
 * Returns:	none.
 * Side effects:
 * Description:
 *		Read data from memory, starting from a given address.
 *		Memory may be Flash (SPI), DRAM (DDR) or SRAM.
 *		The data is written into an output file, data size is limited
 *		as specified.
 *		Data is received in 256 bytes chunks.
 *---------------------------------------------------------------------------
 */
void opr_read_mem(char *output, uint32_t addr, uint32_t size)
{
	FILE *output_file_id = NULL;
	uint32_t cur_addr;
	uint32_t bytes_left;
	uint32_t read_size;
	uint32_t cmd_idx = 1;
	struct command_node rd_cmd_buf;

	if (!console) {
		output_file_id = fopen(output, "w+b");

		if (output_file_id == NULL) {
			display_color_msg(FAIL,
				"ERROR: could not open output file [%s]\n",
				output);
			return;
		}
	}

	DISPLAY_MSG(("Reading [%d] bytes in [%d] packets\n", size,
		((size + (MAX_RW_DATA_SIZE - 1)) / MAX_RW_DATA_SIZE)));

	for (cur_addr = addr; cur_addr < (addr + size);
		cur_addr += MAX_RW_DATA_SIZE) {
		bytes_left = (uint32_t)(addr + size - cur_addr);
		read_size = MIN(bytes_left, MAX_RW_DATA_SIZE);

		cmd_create_read(cur_addr, ((uint8_t)read_size - 1),
					rd_cmd_buf.cmd, &rd_cmd_buf.cmd_size);
		rd_cmd_buf.resp_size = read_size + 3;

		if (opr_send_cmds(&rd_cmd_buf, 1) != true)
			break;

		cmd_disp_read(resp_buf, read_size, cmd_idx,
			((size + (MAX_RW_DATA_SIZE - 1)) / MAX_RW_DATA_SIZE));

		if (console)
			cmd_disp_data((resp_buf + 1), read_size);
		else
			fwrite((resp_buf + 1), 1, read_size, output_file_id);

		cmd_idx++;
	}

	DISPLAY_MSG(("\n"));
	if (!console)
		fclose(output_file_id);
}

/*----------------------------------------------------------------------------
 * Function:	opr_execute_exit
 *
 * Parameters:	addr - Start address to execute from.
 * Returns:	none.
 * Side effects:	ROM-Code is not in UART command mode anymore.
 * Description:
 *	Execute code starting from a given address.
 *	Memory address may be in Flash (SPI), DRAM (DDR) or SRAM.
 *	No further communication with thr ROM-Code is expected at this point.
 *---------------------------------------------------------------------------
 */
void opr_execute_exit(uint32_t addr)
{
	uint32_t cmd_num;

	cmd_build_exec_exit(addr, cmd_buf, &cmd_num);
	if (opr_send_cmds(cmd_buf, cmd_num) != true)
		return;

	cmd_disp_exec_exit(resp_buf);
}

/*----------------------------------------------------------------------------
 * Function:	opr_execute_return
 *
 * Parameters:	addr - Start address to execute from.
 * Returns:	true if successful, false in the case of an error.
 * Side effects:
 * Description:
 *  Execute code starting from the given address and then check the result.
 *  The executed code should return with the execution result.
 *---------------------------------------------------------------------------
 */
bool opr_execute_return(uint32_t addr)
{
	uint32_t cmd_num;

	cmd_build_exec_ret(addr, cmd_buf, &cmd_num);
	if (opr_send_cmds(cmd_buf, cmd_num) != true)
		return false;

	/*
	 * Check the response command code is UFPP_FCALL_RSLT_CMD and
	 * the return value from monitor is 0x03. (program finish and verify ok)
	 */
	if (resp_buf[1] != (uint8_t)(UFPP_FCALL_RSLT_CMD)
					|| resp_buf[2] != 0x03)
		return false;
	return true;
}

/*----------------------------------------------------------------------------
 * Function:	opr_check_sync
 *
 * Parameters:
 *		baudrate - baud rate to check
 *
 * Returns:
 * Side effects:
 * Description:
 *	Checks whether the Host and the Core are synchoronized in the
 *	specified baud rate
 *---------------------------------------------------------------------------
 */
enum sync_result opr_check_sync(uint32_t baudrate)
{
	uint32_t cmd_num;
	struct command_node *cur_cmd = cmd_buf;
	uint32_t bytes_read = 0;
	uint32_t i;

	port_cfg.baudrate = baudrate;
	if (!com_config_uart(port_handle, port_cfg))
		return SR_ERROR;

	cmd_build_sync(cmd_buf, &cmd_num);

	if (!com_port_write_bin(port_handle, cur_cmd->cmd, cur_cmd->cmd_size))
		return SR_ERROR;

	/* Allow several SYNC trials */
	for (i = 0; i < MAX_SYNC_TRIALS; i++) {
		bytes_read = com_port_read_bin(port_handle, resp_buf, 1);

		/* Quit if succeeded to read a response */
		if (bytes_read == 1)
			break;
		/* Otherwise give the ROM-Code time to answer */
		sleep(1);
	}

	if (bytes_read == 0)
		/*
		 * Unable to read a response from ROM-Code in a reasonable
		 * time
		 */
		return SR_TIMEOUT;

	if (resp_buf[0] != (uint8_t)(UFPP_D2H_SYNC_CMD))
		/* ROM-Code response is not as expected */
		return SR_WRONG_DATA;

	/* Good response */
	return SR_OK;
}

/*----------------------------------------------------------------------------
 * Function:	opr_scan_baudrate
 *
 * Parameters:	none
 * Returns:
 * Side effects:
 * Description:
 *		Scans the baud rate range by sending sync request to the core
 *		and prints the response
 *---------------------------------------------------------------------------
 */
bool opr_scan_baudrate(void)
{
	uint32_t baud = 0;
	uint32_t step;
	enum sync_result sr;
	bool synched = false;
	bool data_received = false;

	/* Scan with HUGE STEPS */
	/* BR_BIG_STEP is percents */
	step = (BR_LOW_LIMIT * BR_BIG_STEP) / 100;
	for (baud = BR_LOW_LIMIT; baud < BR_HIGH_LIMIT; baud += step) {
		sr = opr_check_sync(baud);
		step = (baud * BR_BIG_STEP) / 100;
		if (sr == SR_OK) {
			printf("SR_OK: Baud rate - %d, resp_buf - 0x%x\n",
				baud, resp_buf[0]);
			synched = true;
			step = (baud * BR_SMALL_STEP) / 100;
		} else if (sr == SR_WRONG_DATA) {
			printf("SR_WRONG_DATA: Baud rate - %d, resp_buf - "
			       "0x%x\n", baud, resp_buf[0]);
			data_received = true;
			step = (baud * BR_MEDIUM_STEP) / 100;
		} else if (sr == SR_TIMEOUT) {
			printf("SR_TIMEOUT: Baud rate - %d, resp_buf - 0x%x\n",
				baud, resp_buf[0]);

			if (synched || data_received)
				break;
		} else if (sr == SR_ERROR) {
			printf("SR_ERROR: Baud rate - %d, resp_buf - 0x%x\n",
				baud, resp_buf[0]);
			if (synched || data_received)
				break;
		} else
			printf("Unknown error code: Baud rate - %d, resp_buf - "
				"0x%x\n", baud, resp_buf[0]);
	}

	return true;
}

/*----------------------------------------------------------------------------
 * Function:	opr_send_cmds
 *
 * Parameters:	cmd_buf - Pointer to a Command Buffer.
 *		cmd_num - Number of commands to send.
 * Returns:	1 if successful, 0 in the case of an error.
 * Side effects:
 * Description:
 *	Send a group of commands through COM port.
 *	A command is sent only after a valid response for the previous command
 *	was received.
 *---------------------------------------------------------------------------
 */
static bool opr_send_cmds(struct command_node *cmd_buf, uint32_t cmd_num)
{
	struct command_node *cur_cmd = cmd_buf;
	uint32_t cmd;
	uint32_t read;
	time_t start;
	double elapsed_time;

	for (cmd = 0; cmd < cmd_num; cmd++, cur_cmd++) {
		if (com_port_write_bin(port_handle, cur_cmd->cmd,
						cur_cmd->cmd_size) == true) {
			time(&start);

			do {
				read = com_port_wait_read(port_handle);
				elapsed_time = difftime(time(NULL), start);
			} while ((read < cur_cmd->resp_size) &&
				 (elapsed_time <= OPR_TIMEOUT));
			com_port_read_bin(port_handle, resp_buf,
							cur_cmd->resp_size);

			if (elapsed_time > OPR_TIMEOUT)
				display_color_msg(FAIL,
					"ERROR: [%d] bytes received for read, "
					"[%d] bytes are expected\n",
					read, cur_cmd->resp_size);
		} else {
			display_color_msg(FAIL,
				"ERROR: Failed to send Command number %d\n",
				cmd);
			return false;
		}
	}

	return true;
}

