// SPDX-License-Identifier: BSD-2-Clause
/*
 * Copyright (C) 2022 Foundries.io Ltd
 * Jorge Ramirez-Ortiz <jorge@foundries.io>
 */

#include <initcall.h>
#include <kernel/delay.h>
#include <kernel/panic.h>
#include <mm/core_mmu.h>
#include <string.h>
#include <tee/cache.h>
#include "drivers/versal_mbox.h"

#define PM_SIP_SVC	0xc2000000

/* IPI targets */
#define IPI_ID_PMC	1
#define IPI_ID_0	2
#define IPI_ID_RPU0	3
#define IPI_ID_RPU1	4
#define IPI_ID_3	5
#define IPI_ID_4	6
#define IPI_ID_5	7

/* Buffers */
#define IPI_BUFFER_BASEADDR		0xFF3F0000
#define IPI_BUFFER_APU_ID_0_BASE	(IPI_BUFFER_BASEADDR + 0x400)
#define IPI_BUFFER_APU_ID_3_BASE	(IPI_BUFFER_BASEADDR + 0xA00)
#define IPI_BUFFER_APU_ID_4_BASE	(IPI_BUFFER_BASEADDR + 0xC00)
#define IPI_BUFFER_APU_ID_5_BASE	(IPI_BUFFER_BASEADDR + 0xE00)
#define IPI_BUFFER_PMC_BASE		(IPI_BUFFER_BASEADDR + 0x200)
#define IPI_BUFFER_TARGET_APU_OFFSET	0x80
#define IPI_BUFFER_TARGET_PMC_OFFSET	0x40
#define IPI_BUFFER_REQ_OFFSET		0x0
#define IPI_BUFFER_RESP_OFFSET		0x20

#define IPI_BUFFER_LOCAL_OFFSET		IPI_BUFFER_TARGET_APU_OFFSET
#define IPI_BUFFER_REMOTE_OFFSET	IPI_BUFFER_TARGET_PMC_OFFSET

#define IPI_BLOCK		1
#define IPI_NON_BLOCK		0

/* Mailbox api */
enum versal_ipi_api_id {
	IPI_MAILBOX_OPEN = 0x1000,
	IPI_MAILBOX_RELEASE,
	IPI_MAILBOX_STATUS_ENQUIRY,
	IPI_MAILBOX_NOTIFY,
	IPI_MAILBOX_ACK,
	IPI_MAILBOX_ENABLE_IRQ,
	IPI_MAILBOX_DISABLE_IRQ
};

static struct versal_ipi {
	uint32_t lcl;
	const uint32_t rmt;
	paddr_t buf;
	/* Exclusive access to the IPI shared buffer */
	struct mutex lock;
	void *rsp;
	void *req;
} ipi = {
	.buf = IPI_BUFFER_APU_ID_3_BASE,
	.rmt = IPI_ID_PMC,
	.lcl = IPI_ID_3,
};

static const char *const nvm_id[] = {
	[0] = "API_FEATURES",
	[1] = "BBRAM_WRITE_AES_KEY",
	[2] = "BBRAM_ZEROIZE",
	[3] = "BBRAM_WRITE_USER_DATA",
	[4] = "BBRAM_READ_USER_DATA",
	[5] = "BBRAM_LOCK_WRITE_USER_DATA",
	[6] = "EFUSE_WRITE",
	[7] = "EFUSE_WRITE_PUF",
	[8] = "EFUSE_PUF_USER_FUSE_WRITE",
	[9] = "EFUSE_READ_IV",
	[10] = "EFUSE_READ_REVOCATION_ID",
	[11] = "EFUSE_READ_OFFCHIP_REVOCATION_ID",
	[12] = "EFUSE_READ_USER_FUSES",
	[13] = "EFUSE_READ_MISC_CTRL",
	[14] = "EFUSE_READ_SEC_CTRL",
	[15] = "EFUSE_READ_SEC_MISC1",
	[16] = "EFUSE_READ_BOOT_ENV_CTRL",
	[17] = "EFUSE_READ_PUF_SEC_CTRL",
	[18] = "EFUSE_READ_PPK_HASH",
	[19] = "EFUSE_READ_DEC_EFUSE_ONLY",
	[20] = "EFUSE_READ_DNA",
	[21] = "EFUSE_READ_PUF_USER_FUSES",
	[22] = "EFUSE_READ_PUF",
	[23] = "EFUSE_INVALID",
};

static const char *const crypto_id[] = {
	[0] = "FEATURES",
	[1] = "RSA_SIGN_VERIFY",
	[2] = "RSA_PUBLIC_ENCRYPT",
	[3] = "RSA_PRIVATE_DECRYPT",
	[4] = "RSA_KAT",
	[32] = "SHA3_UPDATE",
	[33] = "SHA3_KAT",
	[64] = "ELLIPTIC_GENERATE_PUBLIC_KEY",
	[65] = "ELLIPTIC_GENERATE_SIGN",
	[66] = "ELLIPTIC_VALIDATE_PUBLIC_KEY",
	[67] = "ELLIPTIC_VERIFY_SIGN",
	[68] = "ELLIPTIC_KAT",
	[96] = "AES_INIT",
	[97] = "AES_OP_INIT",
	[98] = "AES_UPDATE_AAD",
	[99] = "AES_ENCRYPT_UPDATE",
	[100] = "AES_ENCRYPT_FINAL",
	[101] = "AES_DECRYPT_UPDATE",
	[102] = "AES_DECRYPT_FINAL",
	[103] = "AES_KEY_ZERO",
	[104] = "AES_WRITE_KEY",
	[105] = "AES_LOCK_USER_KEY",
	[106] = "AES_KEK_DECRYPT",
	[107] = "AES_SET_DPA_CM",
	[108] = "AES_DECRYPT_KAT",
	[109] = "AES_DECRYPT_CM_KAT",
	[110] = "MAX",
};

static const char *const puf_id[] = {
	[0] = "PUF_API_FEATURES",
	[1] = "PUF_REGISTRATION",
	[2] = "PUF_REGENERATION",
	[3] = "PUF_CLEAR_PUF_ID",
};

static const char *const module[] = {
	[5] = "CRYPTO",
	[7] = "FPGA",
	[11] = "NVM",
	[12] = "PUF",
};

static const char *const fpga_id[] = {
	[1] = "LOAD",
};

static void versal_mbox_call_trace(uint32_t call)
{
	uint32_t mid = call >>  8 & 0xff;
	uint32_t api = call & 0xff;
	const char *val = NULL;

	switch (mid) {
	case 5:
		if (api < ARRAY_SIZE(crypto_id))
			val = crypto_id[api];

		break;
	case 7:
		if (api < ARRAY_SIZE(fpga_id))
			val = fpga_id[api];

		break;
	case 11:
		if (api < ARRAY_SIZE(nvm_id))
			val = nvm_id[api];

		break;
	case 12:
		if (api < ARRAY_SIZE(puf_id))
			val = puf_id[api];

		break;
	default:
		break;
	}

	IMSG("--- mbox: service: %s\t call: %s", module[mid],
	     val ? val : "Invalid");
};

static TEE_Result mbox_call(enum versal_ipi_api_id id, uint32_t blocking_call)
{
	struct thread_smc_args args = {
		.a0 = PM_SIP_SVC | id,
		.a1 = reg_pair_to_64(0, ipi.lcl),
		.a2 = reg_pair_to_64(0, ipi.rmt),
		.a3 = reg_pair_to_64(0, blocking_call),
	};

	thread_smccc(&args);

	/* Give the PLM time to access the console */
	if (IS_ENABLED(CFG_VERSAL_TRACE_PLM))
		mdelay(500);

	if (args.a0)
		return TEE_ERROR_GENERIC;

	return TEE_SUCCESS;
}

static TEE_Result versal_mbox_write_req(struct versal_ipi_cmd *cmd)
{
	size_t i = 0;

	for (i = 0; i < VERSAL_MAX_IPI_BUF; i++) {
		if (!cmd->ibuf[i].mem.buf)
			continue;

		if (!IS_ALIGNED((uintptr_t)cmd->ibuf[i].mem.buf,
				CACHELINE_LEN)) {
			EMSG("address not aligned: buffer %zu - %p", i,
			     cmd->ibuf[i].mem.buf);
			return TEE_ERROR_GENERIC;
		}

		if (!IS_ALIGNED(cmd->ibuf[i].mem.alloc_len, CACHELINE_LEN)) {
			EMSG("length not aligned: buffer %zu - %zu",
			     i, cmd->ibuf[i].mem.alloc_len);
			return TEE_ERROR_GENERIC;
		}

		cache_operation(TEE_CACHEFLUSH, cmd->ibuf[i].mem.buf,
				cmd->ibuf[i].mem.alloc_len);
	}

	memcpy(ipi.req, cmd->data, sizeof(cmd->data));

	/* Cache operation on the IPI buffer is safe */
	cache_operation(TEE_CACHEFLUSH, ipi.req, sizeof(cmd->data));

	return TEE_SUCCESS;
}

static TEE_Result versal_mbox_read_rsp(struct versal_ipi_cmd *cmd,
				       struct versal_ipi_cmd *rsp,
				       uint32_t *status)
{
	size_t i = 0;

	/* Cache operation on the IPI buffer is safe */
	cache_operation(TEE_CACHEINVALIDATE, ipi.rsp, sizeof(rsp->data));

	*status = *(uint32_t *)ipi.rsp;

	if (*status)
		return TEE_ERROR_GENERIC;

	if (rsp)
		memcpy(rsp->data, ipi.rsp, sizeof(rsp->data));

	for (i = 0; i < VERSAL_MAX_IPI_BUF; i++) {
		if (!cmd->ibuf[i].mem.buf)
			continue;

		if (!IS_ALIGNED((uintptr_t)cmd->ibuf[i].mem.buf,
				CACHELINE_LEN)) {
			EMSG("address not aligned: buffer %zu - %p",
			     i, cmd->ibuf[i].mem.buf);
			return TEE_ERROR_GENERIC;
		}

		if (!IS_ALIGNED(cmd->ibuf[i].mem.alloc_len, CACHELINE_LEN)) {
			EMSG("length not aligned: buffer %zu - %zu",
			     i, cmd->ibuf[i].mem.alloc_len);
			return TEE_ERROR_GENERIC;
		}

		cache_operation(TEE_CACHEINVALIDATE,
				cmd->ibuf[i].mem.buf,
				cmd->ibuf[i].mem.alloc_len);
	}

	return TEE_SUCCESS;
}

TEE_Result versal_mbox_alloc(size_t len, const void *init,
			     struct versal_mbox_mem *mem)
{
	mem->buf = memalign(CACHELINE_LEN, ROUNDUP(len, CACHELINE_LEN));
	if (!mem->buf)
		panic();

	memset(mem->buf, 0, ROUNDUP(len, CACHELINE_LEN));

	if (init)
		memcpy(mem->buf, init, len);

	mem->alloc_len = ROUNDUP(len, CACHELINE_LEN);
	mem->len = len;

	return TEE_SUCCESS;
}

TEE_Result versal_mbox_notify(struct versal_ipi_cmd *cmd,
			      struct versal_ipi_cmd *rsp, uint32_t *err)
{
	TEE_Result ret = TEE_SUCCESS;
	uint32_t remote_status = 0;

	mutex_lock(&ipi.lock);

	ret = versal_mbox_write_req(cmd);
	if (ret) {
		EMSG("Can't write the request command");
		goto out;
	}

	if (IS_ENABLED(CFG_VERSAL_TRACE_MBOX))
		versal_mbox_call_trace(cmd->data[0]);

	ret = mbox_call(IPI_MAILBOX_NOTIFY, IPI_BLOCK);
	if (ret) {
		EMSG("IPI error");
		goto out;
	}

	ret = versal_mbox_read_rsp(cmd, rsp, &remote_status);
	if (ret)
		EMSG("Can't read the remote response");

	if (remote_status) {
		if (err)
			*err = remote_status;
		/*
		 * Check the remote code (FSBL repository) in xplmi_status.h
		 * and the relevant service error (ie, xsecure_error.h) for
		 * detailed information.
		 */
		DMSG("PLM: plm status = 0x%" PRIx32 ", lib_status = 0x%" PRIx32,
		     (remote_status & 0xFFFF0000) >> 16,
		     (remote_status & 0x0000FFFF));

		ret = TEE_ERROR_GENERIC;
	}
out:
	mutex_unlock(&ipi.lock);

	return ret;
}

static TEE_Result versal_mbox_init(void)
{
	switch (CFG_VERSAL_MBOX_IPI_ID) {
	case 0:
		ipi.buf = IPI_BUFFER_APU_ID_0_BASE;
		ipi.lcl = IPI_ID_0;
		break;
	case 3:
		break;
	case 4:
		ipi.buf = IPI_BUFFER_APU_ID_4_BASE;
		ipi.lcl = IPI_ID_4;
		break;
	case 5:
		ipi.buf = IPI_BUFFER_APU_ID_5_BASE;
		ipi.lcl = IPI_ID_5;
		break;
	default:
		EMSG("Invalid IPI requested");
		return TEE_ERROR_GENERIC;
	}

	ipi.req = core_mmu_add_mapping(MEM_AREA_RAM_SEC,
				       ipi.buf + IPI_BUFFER_REMOTE_OFFSET +
				       IPI_BUFFER_REQ_OFFSET,
				       sizeof(struct versal_ipi_cmd));

	ipi.rsp = core_mmu_add_mapping(MEM_AREA_RAM_SEC,
				       ipi.buf + IPI_BUFFER_REMOTE_OFFSET +
				       IPI_BUFFER_RESP_OFFSET,
				       sizeof(struct versal_ipi_cmd));
	if (!ipi.req || !ipi.rsp)
		panic();

	mutex_init(&ipi.lock);

	return mbox_call(IPI_MAILBOX_OPEN, IPI_BLOCK);
}
early_init(versal_mbox_init);