/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* SPDX-License-Identifier:	GPL-2.0+
*/


#include <common.h>
#include <fastboot.h>
#include <fb_otp.h>
#include <fastboot-internal.h>
#include <ring_platform_features.h>
#include <nand.h>
#include <stdlib.h>
#include <spinand.h>
#include <drvSPINAND.h>

#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)

static int metadata_state = ERR_METADATA_GENERAL;
static u8 metadata_page[MAX_PAGE_SZ]; /* 1 NAND page */

/*
 * determine if the partition name is OTP
 */

int is_otp_region(char *cmd_parameter) {
	if (!strncmp(cmd_parameter, OTPNAME, strlen(OTPNAME))) {
		return 1;
	}

	return 0;
}

int write_nand_otp(void *fastboot_buf_addr, u32 image_size) {
	u32 ret = -1;
	loff_t off = 0;
	int dev = 0;
	nand_info_t *nand = &nand_info[dev];

	if (!is_otp_programmed()) {
		if (image_size > (MAX_PAGE_SZ * MAX_OTP_PAGES)) {
			printf("Image size (%d) larger than max size (%d)", image_size,
					(MAX_PAGE_SZ * MAX_OTP_PAGES));
			return ret;
		}

		ret = MDrv_SPINAND_EnableOtp(true);
		if (ret) {
			printf("Error switching to OTP mode: %d\n", ret);
			return ret;
		}

		loff_t base = MDrv_SPINAND_GetOTPPageOffset(false, false);
		if (MDrv_SPINAND_OTPInNormalBlock() && nand_block_isbad(nand, base * MAX_PAGE_SZ)) {
			base = MDrv_SPINAND_GetOTPPageOffset(true, false);
		}

		const size_t nCopies = MDrv_SPINAND_GetOTPMaxBackup();

		for (size_t i = 0; i < nCopies; i++) {
			off = (MAX_PAGE_SZ * (base + i * OTP_GROUP_SIZE));
			ret = nand_write_skip_bad(nand, off, &image_size, NULL, image_size,
					(u_char *)fastboot_buf_addr, 0);

			if (ret)
				printf("RW fail %d\n", ret);
		}

		ret = MDrv_SPINAND_EnableOtp(false);
		if (ret)
			printf("Error switch from OTP mode: %d\n", ret);

		metadata_state = ERR_METADATA_GENERAL;
	} else {
		uint8_t *written_data = malloc(MAX_PAGE_SZ);
		size_t i;

		ret = 0;
		printf("OTP aleady programmed. Comparing...\n");
		for (i = 0; i < (image_size + (MAX_PAGE_SZ - 1)) / MAX_PAGE_SZ; i++) {
			int cmpret = 0;
			u32 compare_size = MAX_PAGE_SZ;
			read_otp_page(i, written_data, MAX_PAGE_SZ, 0);
			if (i == 0) {
				/* Only compare the data in the otp_meta
				   structure.  The rest of the first page is
				   random data, so can only be compared if we
				   have retained the original file */
				compare_size = sizeof(struct otp_meta);
				struct otp_meta *otp_meta_ptr = (struct otp_meta *)written_data;
				if (otp_meta_ptr->version < 2) {
					compare_size -= sizeof(otp_meta_ptr->prod_keys_sig);
				}
			}
			cmpret = memcmp(written_data, fastboot_buf_addr + (i * MAX_PAGE_SZ),
					compare_size);
			printf("Page %d %s\n", i, cmpret ? "MISMATCHED!" : "Compared OK");
			if (cmpret)
				ret = -1;
		}

		free(written_data);
	}

	return ret;
}

int fb_otp_nand_lock(void)
{
	if (MDrv_SPINAND_LockOtp() == ERR_SPINAND_SUCCESS)
		return 0;

	return -1;
}

int read_otp_metadata_page() {
	if (metadata_state == ERR_METADATA_GENERAL) {
		if (read_otp_page(META_DATA_PAGE, metadata_page, sizeof(metadata_page), 0)) {
			printf("Failed to read META page from NAND OTP\n");
			metadata_state = ERR_METADATA_READ_FAIL;
			goto out;
		}
		if(!strncmp((char *)metadata_page, COCOA_MAGIC, strlen(COCOA_MAGIC))) {
			metadata_state = ERR_METADATA_READ_OK;
		} else {
			metadata_state = ERR_METADATA_NOT_PROGRAMMED;
		}
	}
out:
	return metadata_state;
}

bool is_otp_locked(void)
{
	const size_t otp_pages = 3;
	const size_t page_size = 2048;
	const size_t load_size = page_size * otp_pages;
	u8 *load_otp = NULL;

	if (MDrv_SPINAND_OTPInNormalBlock())
	{
		load_otp = malloc(load_size);
		for (size_t i = 0; i < otp_pages; i++) {
			read_otp_page(i, load_otp + (i * page_size), page_size, 0);
		}
	}
	bool ret = MDrv_SPINAND_IsOtpLocked();
	if (MDrv_SPINAND_OTPInNormalBlock())
	{
		// In some case we erase the OTP data in blocks to get lock status, (e.g. for Toshiba flash)
		// we need to clear cached metadata as well.
		metadata_state = ERR_METADATA_GENERAL;
		if (!ret)
		{
			write_nand_otp(load_otp, load_size);
		}
		free(load_otp);
	}
	return ret;
}

bool is_otp_programmed() {
	int err = read_otp_metadata_page();
	bool ret = false;

	if (err == ERR_METADATA_READ_OK) {
		printf("OTP programmed!\n");
		ret = true;
	} else if (err == ERR_METADATA_NOT_PROGRAMMED) {
		printf("OTP Not programmed!\n");
	} else {
		printf("Unknown OTP state: err %d\n", err);
	}
	return ret;
}

int try_otp_read_all(char *dsn, char *boardid) {
	int err = read_otp_metadata_page();
	if (err == ERR_METADATA_READ_OK) {
		u8 *meta_lcl = metadata_page;
		meta_lcl += strlen(COCOA_MAGIC) + COCOA_VER_LEN;
		if (boardid) {
			strncpy(boardid, meta_lcl, COCOA_BOARD_ID_LEN);
			boardid[COCOA_BOARD_ID_LEN] = '\0';
		}
		meta_lcl += COCOA_BOARD_ID_LEN;
		if (dsn) {
			strncpy(dsn, meta_lcl, COCOA_DSN_LEN);
			dsn[COCOA_DSN_LEN] = '\0';
		}
	} else if (err == ERR_METADATA_NOT_PROGRAMMED) {
#if (defined COCOA_DEFAULT_DEVICE_DSN) && (defined COCOA_DEFAULT_DEVICE_BOARD_ID)
		printf("Failed to read metadata from OTP - use build defaults\n");
		if (dsn) {
			memcpy(dsn, STR(COCOA_DEFAULT_DEVICE_DSN), COCOA_DSN_LEN);
			dsn[COCOA_DSN_LEN] = '\0';
		}
		if (boardid) {
			memcpy(boardid, STR(COCOA_DEFAULT_DEVICE_BOARD_ID), COCOA_BOARD_ID_LEN);
			boardid[COCOA_BOARD_ID_LEN] = '\0';
		}
		err = ERR_METADATA_USE_DEFAULTS;
#else
		printf("No metadata found in OTP\n");
#endif
	}
	return err;
}

int try_otp_read_dsn(char *dsn) {
	return try_otp_read_all(dsn, NULL);
}

int try_otp_read_version(uint32_t *version) {
	int err = read_otp_metadata_page();
	if (err == ERR_METADATA_READ_OK && version) {
		*version = ((struct otp_meta *) metadata_page)->version;
	}
	return err;
}
