/******************************************************************************
  Firmware downloader for ASI2400

Copyright (C) 1997-2017 AudioScience, Inc. All rights reserved.

This software is provided 'as-is', without any express or implied warranty.
In no event will AudioScience Inc. be held liable for any damages arising
from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software
   in a product, an acknowledgment in the product documentation would be
   appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.
3. This copyright notice and list of conditions may not be altered or removed
   from any source distribution.

AudioScience, Inc. <support@audioscience.com>

( This license is GPL compatible see http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses )

*******************************************************************************/
#include <string.h>
#include <stdlib.h>

#include "hpi_internal.h"
#include "hpimsginit.h"
#include "hpidebug.h"
#include "hpinet.h"

#include "hpifirmware.h"

/*---------------------------------------------------------------------------*/

#define BOOTLOADER_OFFSET 0
#define FACTORY_OFFSET 0x08000
#define UPDATE_OFFSET  0x80000

/* max 999 */
#define MSG_DELAY 10	// milliseconds

/*---------------------------------------------------------------------------*/

struct asi_code_header{
	uint32_t size; /**< Size in bytes including header */
	uint32_t type; /**< File type tag "CODE" == 0x45444F43 */
	uint32_t firmware_id; /**< Firmware family ID e.g 0x6600 */
	uint32_t version; /**< Firmware version*/
	uint32_t checksum; /**< Data checksum */
};

struct dspbin_tailer{
	uint32_t checksum;
	uint32_t size;
	uint32_t version;
	uint32_t firmware_id;
	uint32_t type; /**< File type tag "CODE" == 0x45444F43 */
};


struct fw_info {
	enum eHpiFirmwareId type;
	uint32_t offset;
	uint32_t key;
	char name[12];
};

#define MAX_FILES 3
static struct fw_info fw[MAX_FILES] = {
	{HPI_FW_UPDATE, UPDATE_OFFSET, HPI_FLASH_KEY_FIRMWARE_USER, "Update"},
	{HPI_FW_FACTORY, FACTORY_OFFSET, HPI_FLASH_KEY_FIRMWARE_FACT, "Factory"},
	{HPI_FW_BOOTLOADER, BOOTLOADER_OFFSET, HPI_FLASH_KEY_FIRMWARE_BOOT, "Bootloader"}
};

/*---------------------------------------------------------------------------*/
/**

*/
static uint32_t calc_checksum(uint32_t checksum, int count, uint32_t *pData)
{
	int i;
	for (i = 0; i < count; i++)
		checksum ^= pData[i];

	return checksum;
}

/*---------------------------------------------------------------------------*/
/**

*/
static void HPI_CALLBACK null_progress_callback(char *state, uint32_t percent)
{
	HPI_UNUSED(state);
	HPI_UNUSED(percent);
}

static void HPI_CALLBACK null_progressex_callback(enum eHpiFirmwareStatus status, uint32_t percent, void *user_arg)
{
	HPI_UNUSED(status);
	HPI_UNUSED(percent);
	HPI_UNUSED(user_arg);
}

/** number of words in code tailer */
#define TAILERSIZE 5

/** extract family and version from filename
Expects file of form ".../blah/dsp1234_98765.xxx"
Returns adapter_family = 0x1234, version = 98765
*/
static void decode_name(const char *firmware_filename,
	uint16_t *adapter_family,
	int *version)
{
	const char *p;
	char *ep;
	int l;
	long int fam = 0, ver = 0;

	l = strlen(firmware_filename);
	p = firmware_filename + l;

	// search backwards for path separator or start of string
	while (p > firmware_filename) {
		p--;
		if ((*p == '/') || (*p == '\\')) {
			p++;
			break;
		}
	}
	if (strlen(p) >= 11) { // at least dspNNNN.XXX
		while ((*p < '1') || (*p > '9'))
			p++;
		fam = strtol(p, &ep, 16);
		if (*ep == '_') { // Version follows '_'
			p = ep + 1;
			ver = strtol(p, &ep, 16);
		}
	}

	*adapter_family = (uint16_t)fam;
	*version = ver;
}

/*---------------------------------------------------------------------------*/
/** Open firmware file, read header and do basic checks
 * On error, file is closed, otherwise left open for further reading,
 * starting with the first byte of the actual image to be downloaded.
 * header.size is adjusted to be image size excluding itself.
 *
 * All the current header and tailer variants are normalised here
 */
static hpi_err_t firmware_open_header(
	const char *firmware_filename,
	uint16_t adapter_family,
	FILE **firmware_file,
	struct asi_code_header *header,
	int * tailer_id)
{
	hpi_err_t err;
	int got_header = 0;
	uint32_t tailer[5];

	memset(header, 0, sizeof(*header));
	*tailer_id = 0;

	if ((err = HpiOs_fopen_rb(firmware_filename, firmware_file)))
		return err;

	// look for dspbin compatible code tailer
	HpiOs_fseek(*firmware_file, 0 - sizeof(tailer), SEEK_END);
	if (HpiOs_fread(&tailer, 1, sizeof(tailer), *firmware_file) < sizeof(tailer)) {
		HpiOs_fclose(*firmware_file);
		return HPI_ERROR_DSP_FILE_NO_HEADER;
	}

	if (tailer[4] == 0x45444F43) { // CODE tailer
		*tailer_id = 0x45444F43;
		//printf("Found CODE tailer\n");
	}
	if (tailer[0] == 0x31495341) { // ASI1 tailer
		*tailer_id = 0x31495341;
		//printf("Found ASI1 tailer\n");
	}
	HpiOs_fseek(*firmware_file, 0, SEEK_SET);

	// look for orignal dspbin header, so it can be skipped
	if (HpiOs_fread(header, 1, sizeof(*header), *firmware_file) < sizeof(*header)) {
		HpiOs_fclose(*firmware_file);
		return HPI_ERROR_DSP_FILE_NO_HEADER;
	}
	if (header->type == 0x45444F43) {
		 // Found original dspbin "CODE" header : its an ASI dsp code file
		//printf("Found CODE header\n");
		got_header = 1;
		header->size -= sizeof(header);
		// leave file ready to read payload
	} else {
		// translate tailer fields into header fields
		if (*tailer_id == 0x45444F43) {
			header->checksum = tailer[0];
			header->size = tailer[1];
			header->version = tailer[2];
			header->firmware_id = tailer[3];
			header->type = tailer[4];
			got_header = 1;
		}
		if (*tailer_id == 0x31495341) {
			header->checksum = tailer[1];
			header->size = tailer[2];
			header->version = tailer[3];
			header->firmware_id = tailer[4];
			header->type = tailer[0];
			got_header = 1;
		}
		HpiOs_fseek(*firmware_file, 0, SEEK_SET);
	}

	// Still got no metadata,  maybe it is just a bare AIS image
	if (!got_header &&(header->size == 0x41504954) ) {
		// "TIPA" : its an AIS image, no header
		uint32_t buf[1024];
		uint32_t nread, nwords = 0;
		uint32_t checksum = 0;
		uint16_t file_family;
		int file_version;

		printf("No file header or tailer found\nGenerating header from filename and contents\n");
		/* other option here would be to read the metadata tailer */
		decode_name(firmware_filename, &file_family, &file_version);

		/* get info for header struct from file */
		HpiOs_fseek(*firmware_file, 0, SEEK_SET);
		while ((nread = HpiOs_fread(buf, 4, 1024, *firmware_file))) {
			nwords += nread;
			checksum = calc_checksum(checksum, nread, buf);
		}

		HpiOs_fseek(*firmware_file, 0, SEEK_SET);
		header->size = nwords * 4;
		header->version = file_version;
		header->checksum = checksum;
		header->firmware_id = file_family;
		if (file_family)
			got_header = 1;
	}

	if (!got_header) {
		HpiOs_fclose(*firmware_file);
		return HPI_ERROR_DSP_FILE_NO_HEADER;
	}

	if (header->firmware_id == adapter_family)
		return 0;
	else {
		// printf("File family %04X didn't match requested %04X\n", header->firmware_id, adapter_family);
		HpiOs_fclose(*firmware_file);
		return HPI_ERROR_DSP_SECTION_NOT_FOUND;
	}
}

/*---------------------------------------------------------------------------*/
static hpi_err_t firmware_download(
	uint16_t adapter_index,
	enum eHpiFirmwareId firmware_id,
	const char *firmware_filename,
	uint16_t bootload_family,
	progress_callback * pc,
	progress_callbackex * pcex,
	void *user_arg)
{
	hpi_err_t err;
	FILE *firmware_file;
	uint32_t nwords;
	uint32_t checksum;
	uint32_t key;
	uint32_t total_checksum = 0;
	uint32_t start_offset;
	uint32_t sequence = 0;
	uint32_t progress = 0;
	uint32_t p_inc;
	int tail_written = 0;
	int has_tailer;
	size_t nbytes;
	struct  hpi_msg_adapter_program_flash hm;
	struct  hpi_res_adapter_program_flash hr;
	struct asi_code_header header;
	struct hpi_msg_adapter_flash hmsf;
	struct hpi_res_adapter_flash hrsf;

	HpiOs_DelayMicroSeconds(MSG_DELAY * 1000);

	if (!pc)
		pc = &null_progress_callback;
	if (!pcex)
		pcex = &null_progressex_callback;

	/* WARNING assumes knowledge of how the enums are ordered? */
	if ((firmware_id > HPI_FW_BOOTLOADER) || (firmware_id < HPI_FW_UPDATE)){
		err = HPI_ERROR_INVALID_OBJ_INDEX;
		goto error_exit;
	}

	start_offset = fw[firmware_id - 1].offset;
	// printf("Downloading %s image\n", fw[firmware_id - 1].name);

	err = firmware_open_header(firmware_filename, bootload_family, &firmware_file, &header, &has_tailer);
	if (err)
		goto error_exit;

	nwords = header.size / sizeof(uint32_t);

	if (!has_tailer)
		nwords += TAILERSIZE;

	if (bootload_family == 0x2400)
		key = HPI_FLASH_KEY_FIRMWARE_ASI;
	else
		key =  fw[firmware_id - 1].key;

	progress = 0;
	pc("Erasing firmware", 0);
	pcex(HPI_FW_PROGSTATUS_ERASING, 0, user_arg);

	sequence = 200;
	do {
		HPI_InitMessageResponse(
				(HPI_MESSAGE *)&hmsf,
				(HPI_RESPONSE *)&hrsf,
				HPI_OBJ_ADAPTER,
				HPI_ADAPTER_START_FLASH);

		hmsf.h.wAdapterIndex = adapter_index;
		hmsf.dwKey = key;
		if (bootload_family == 0x2400)
			hmsf.dwOffset = start_offset;
		hmsf.dwLength = nwords;

		hrsf.h.wError = 0;
		hrsf.h.wSize = sizeof(hrsf);
		// still need to allow for up to 10 sec for flash erase
		// for firmware that doesn't return busy status
		HPI_MessageUDP((HPI_MESSAGE *)&hmsf, (HPI_RESPONSE *)&hrsf, 10000);
		err = hrsf.h.wError;
		if ((err == HPI_ERROR_NVMEM_BUSY) &&
		    (progress != hrsf.h.wSpecificError)) {
			progress = hrsf.h.wSpecificError;
			pc("Erasing firmware", progress);
			pcex(HPI_FW_PROGSTATUS_ERASING, progress, user_arg);
		}

		HpiOs_DelayMicroSeconds(100000);
	} while (sequence-- && (err == HPI_ERROR_NVMEM_BUSY));

	pc("\nProgramming", 0);
	pcex(HPI_FW_PROGSTATUS_PROGRAMMING, 0,user_arg);
	p_inc = 9000 * ARRAY_SIZE(hm.data)/ nwords;
	nbytes = HpiOs_fread(hm.data, 1, sizeof(hm.data), firmware_file);
	sequence = 0;
	while (nbytes  && !err) {
		int retries;
		size_t nwords = (nbytes + 3) / 4;

		checksum = calc_checksum(0, nwords, hm.data);
		total_checksum ^= checksum;

		HPI_InitMessageResponse(
				(HPI_MESSAGE *)&hm,
				(HPI_RESPONSE *)&hr,
				HPI_OBJ_ADAPTER,
				HPI_ADAPTER_PROGRAM_FLASH);
		hm.h.wAdapterIndex = adapter_index;
		hm.p.wSequence = (uint16_t)sequence;
		hm.p.dwChecksum = checksum;
		hm.p.wLength = (uint16_t)nwords;
		hm.p.wOffset = offsetof(struct hpi_msg_adapter_program_flash, data);
		hm.h.wSize = hm.p.wOffset + nbytes;

		retries = 5;

		while (retries) {
			hr.h.wError = 0;
			hr.h.wSize = sizeof(hr);
			HPI_MessageUDP((HPI_MESSAGE *)&hm, (HPI_RESPONSE *)&hr, 2000);
			HpiOs_DelayMicroSeconds(MSG_DELAY * 1000);

			retries--;
			err = hr.h.wError;
			/* if ((sequence==3) && (retries == 4)) err = 1; // Test that repeated packet works  */
			if (!err)
				break;
			/*printf("Retry %d, seq %d\n",retries,sequence);*/
		}

		sequence++;
		progress += p_inc;
		pc("Programming", 10 + progress / 100);
		pcex(HPI_FW_PROGSTATUS_PROGRAMMING, 10 + progress / 100,user_arg);

		nbytes = HpiOs_fread(hm.data, 1, sizeof(hm.data), firmware_file);

		if (!nbytes) {
			if (!has_tailer && !tail_written) {
				hm.data[0] = total_checksum;
				hm.data[1] = header.size;
				hm.data[2] = header.version;
				hm.data[3] = header.firmware_id;
				hm.data[4] = header.type;
				printf("Generated Tailer %x %d %x %x %x\n", hm.data[0], hm.data[1], hm.data[2], hm.data[3], hm.data[4]);
				nbytes = TAILERSIZE * sizeof(uint32_t);
				tail_written = 1;
			}
		}
	}

	HpiOs_fclose(firmware_file);

	// send end flash message
	HPI_InitMessageResponse(
		(HPI_MESSAGE *)&hm,
		(HPI_RESPONSE *)&hr,
		HPI_OBJ_ADAPTER,
		HPI_ADAPTER_END_FLASH);

	hm.h.wAdapterIndex = adapter_index;
	hm.p.wSequence = (uint16_t)sequence;

	hr.h.wError = 0;
	hr.h.wSize = sizeof(hr);
	HPI_MessageUDP((HPI_MESSAGE *)&hm, (HPI_RESPONSE *)&hr, 1000);

	if (err)
		goto error_exit;

	pc("\nDone", 100);
	pcex(HPI_FW_PROGSTATUS_DONE, 100, user_arg);
	return err;

error_exit:
	pc("\nFailed with error", err);
	pcex(HPI_FW_PROGSTATUS_FAILURE, err, user_arg);

	return err;
}

/*---------------------------------------------------------------------------*/
/**

*/
HPI_API (hpi_err_t) HPI_AdapterFirmwareDownload(
	uint16_t adapter_index,
	enum eHpiFirmwareId firmware_id,
	const char *firmware_filename,
	uint16_t bootload_family,
	uint16_t bootload_family_mask,
	progress_callback * pc)
{
	return firmware_download(adapter_index, firmware_id, firmware_filename, bootload_family,
				pc, NULL, NULL);
}

/*---------------------------------------------------------------------------*/
HPI_API (hpi_err_t) HPI_AdapterFirmwareDownloadEx(
	uint16_t adapter_index,
	enum eHpiFirmwareId firmware_id,
	const char *firmware_filename,
	uint16_t bootload_family,
	uint16_t bootload_family_mask,
	progress_callback * pc,
	progress_callbackex * pcex,
	void *user_arg)
{
	return firmware_download(adapter_index, firmware_id, firmware_filename, bootload_family,
				pc, pcex, user_arg);
}

/*---------------------------------------------------------------------------*/
HPI_API (hpi_err_t) HPI_FileFirmwareGetInfo(const char *firmware_filename,
	uint16_t adapter_family,
	uint16_t adapter_family_mask,
	unsigned int *adapter,
	unsigned int *version,
	unsigned int *checksum,
	unsigned int *length)
{
	hpi_err_t err;
	FILE *firmware_file;
	struct asi_code_header header;
	int has_tailer;

	err = firmware_open_header(firmware_filename, adapter_family, &firmware_file, &header, &has_tailer);

	*version = header.version;
	*checksum = header.checksum;
	*length =  header.size;
	*adapter = header.firmware_id;
	return err;
}

/*---------------------------------------------------------------------------*/
HPI_API (hpi_err_t) HPI_AdapterFirmwareGetInfo2(
	uint16_t adapterIndex,
	enum eHpiFirmwareId firmware_id,
	unsigned int *version,
	unsigned int *checksum,
	unsigned int *length,
	unsigned int *family,
	unsigned int *max_length
)
{
	struct hpi_msg_adapter_flash hm;
	struct hpi_res_adapter_flash hr;
	uint16_t bootload_family;
	uint16_t ignore;
	uint32_t ignore32;
	hpi_err_t err;

	/* WARNING assumes knowledge of how the enums are ordered? */
	if ((firmware_id > HPI_FW_BOOTLOADER) || (firmware_id < HPI_FW_UPDATE))
		return HPI_ERROR_INVALID_OBJ_INDEX;

	err = HPI_AdapterGetInfo(NULL, adapterIndex,
                     &ignore, &ignore, &ignore, &ignore32, &bootload_family);
	if (err)
		return err;

	switch (HPI_ADAPTER_FAMILY_ASI(bootload_family))
	{
		case HPI_ADAPTER_FAMILY_ASI(0x2300):
			bootload_family = bootload_family & 0xFFF0;
			break;
		default:
			bootload_family = HPI_ADAPTER_FAMILY_ASI(bootload_family);
			break;
	}

	HPI_InitMessageResponse(
			(HPI_MESSAGE *)&hm,
			(HPI_RESPONSE *)&hr,
			HPI_OBJ_ADAPTER,
			HPI_ADAPTER_QUERY_FLASH);
	hm.h.wAdapterIndex = adapterIndex;
	hm.dwOffset = fw[firmware_id - 1].offset;

	if (bootload_family == 0x2400)
		hm.dwKey = HPI_FLASH_KEY_FIRMWARE_ASI;
	else
		hm.dwKey =  fw[firmware_id - 1].key;

	hr.h.wSize = sizeof(hr);

	HPI_MessageUDP((struct hpi_message *)&hm, (struct hpi_response *)&hr, 1000);

	if (hr.dwFamily & 0xFFFF000F)
		hr.dwFamily = 0;
	if (hr.dwMaxLength & 0x00000FFF)
		hr.dwMaxLength = 0;

	*version = hr.dwVersion;
	*checksum = hr.dwChecksum;
	*length = hr.dwLength;
	*family = hr.dwFamily;
	*max_length =  hr.dwMaxLength;

	return hr.h.wError;
}

HPI_API (hpi_err_t) HPI_AdapterFirmwareGetInfo(
	uint16_t adapterIndex,
	enum eHpiFirmwareId firmware_id,
	unsigned int *version,
	unsigned int *checksum,
	unsigned int *length)
{
	unsigned int family, max_len;

	return HPI_AdapterFirmwareGetInfo2(
			adapterIndex,	firmware_id,
			version, checksum, length,
			&family, &max_len);

}
