/**
 * Copyright (C) 2014 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Mobile Communication Division,
 * Digital Media & Communications Business, Samsung Electronics Co., Ltd.
 *
 * This software and its documentation are confidential and proprietary
 * information of Samsung Electronics Co., Ltd.  No part of the software and
 * documents may be copied, reproduced, transmitted, translated, or reduced to
 * any electronic medium or machine-readable form without the prior written
 * consent of Samsung Electronics.
 *
 * Samsung Electronics makes no representations with respect to the contents,
 * and assumes no responsibility for any errors that might appear in the
 * software and documents. This publication and the contents hereof are subject
 * to change without notice.
 *
 */
/**
 * @file hdcp2_global.cpp
 * @author
 * @date
 * @brief This file acts as an interface between NWD and SWD when Global APIs are used
 */

#include "hdcp2_global.h"
#include <sys/mman.h>
#include "ion.h"
#include "ion_4.12.h"

#ifdef USE_MTK
#include "MTK/ion_drv.h"
#endif /* USE_MTK */

#ifdef USE_SYSTEM_PARTITION
#include "tees_client_api.h"
#endif /* USE_SYSTEM_PARTITION */

#define DUMP_HEX

TEEC_UUID ga_hdcp2_transmitter_uuid = {
	ga_hdcp2_transmitter_uuid.timeLow = 0,
	ga_hdcp2_transmitter_uuid.timeMid = 0,
	ga_hdcp2_transmitter_uuid.timeHiAndVersion = 0,
	ga_hdcp2_transmitter_uuid.clockSeqAndNode[0]=0,
	ga_hdcp2_transmitter_uuid.clockSeqAndNode[1]=0,
	ga_hdcp2_transmitter_uuid.clockSeqAndNode[2]=0,
	ga_hdcp2_transmitter_uuid.clockSeqAndNode[3]=0,
	ga_hdcp2_transmitter_uuid.clockSeqAndNode[4]=0x48,
	ga_hdcp2_transmitter_uuid.clockSeqAndNode[5]=0x44,
	ga_hdcp2_transmitter_uuid.clockSeqAndNode[6]=0x43,
	ga_hdcp2_transmitter_uuid.clockSeqAndNode[7]=0x50,
};

TEEC_Context ga_context = {0}; //context to be initialized
TEEC_Session ga_session = {0}; // session id for trustlet session
TEEC_SharedMemory ga_sharedmem = {0}; //shared memory
TEEC_SharedMemory ga_sharedmem_res = {0}; // shared memory for response
TEEC_Operation ga_operation = {0}; // operation to be sent in invoke command
uint32_t ga_returnOrigin = 0;

struct ionfd2phys {
	int fd;
	size_t nr_pfns;
	unsigned long *pfns;
};

unsigned long *buf_pfns = NULL;
unsigned long *allocated_buf_pfns = NULL;
int ion_fd = 0;
int dev_ion_fd = 0;
int allocated_ion = 0;

typedef struct ion_buffer {
	uint8_t *physaddr;
	uint8_t *viraddr;
	uint32_t length;
	int fd;
} ion_buffer_t;

ion_buffer_t ion_buf;

#define ION_HEAP_EXYNOS_CONTIG_MASK (1 << 4)
#define TEEGRIS_ION_FLAG_FORCE_CONTIGUOUS (1 << 30)
#define TEEGRIS_ION_FLAG_CACHED 0x01

#ifdef USE_MTK
#define ION_HEAP_MULTIMEDIA_CONTIG_MASK (1 << 15)
#define ION_HEAP_DMA_RESERVED_MASK (1 << 16)
#endif /* USE_MTK */

/**
 * @fn static int alloc_buf(int len, int *fd)
 * @brief This function allocates buffer and map physical address to virtual address.
 * @param len - size of buffer
 * @param fd - allocated fd
 * @return int - returns 0 if success else returns corresponding error code.
 */
static int alloc_buf(int len, int *fd)
{
	int ret = HDCP2_OK;

	if (len <= 0 || fd == NULL) {
		HDCP2_Log("Invalid input in alloc_buf(). len:%x, fd:%x", len, fd);
		return HDCP2_ERR_INVALID_INPUT;
	}

	dev_ion_fd = open("/dev/ion", O_RDWR);
	if (dev_ion_fd < 0) {
		HDCP2_Log("alloc_buf: dev open fail %s.", strerror(errno));
		return HDCP2_ERR;
	}

#if defined(USE_EXYNOS)
	if (!ion_is_legacy(dev_ion_fd)) {
		int heap_cnt = 0;
		unsigned int heap_id = 0;

		if (ion_query_heap_cnt(dev_ion_fd, &heap_cnt) < 0 || heap_cnt <= 0) {
			HDCP2_Log("Fail to query the heap count. heap_cnt : %d", heap_cnt);
		}

		struct ion_heap_data heaps[heap_cnt];

		if (ion_query_get_heaps(dev_ion_fd, heap_cnt, heaps) < 0) {
			HDCP2_Log("fail to query the heaps");
		}

		for (int i = 0; i < heap_cnt; i++) {
			if (strcmp(heaps[i].name, "crypto_heap") == 0)
				heap_id = heaps[i].heap_id; 
		}

		ret = ion_alloc_fd(dev_ion_fd, len, 0, 1 << heap_id, TEEGRIS_ION_FLAG_CACHED, fd);
	} else {
		HDCP2_Log("PVV: ion_alloc_fd:  Exynos");
		ret = ion_alloc_fd(dev_ion_fd, len, 0, ION_HEAP_EXYNOS_CONTIG_MASK, TEEGRIS_ION_FLAG_CACHED, fd);
	}
#elif defined(USE_MTK)
	HDCP2_Log("PVV: ion_alloc_fd:  MTK");
	ret = ion_alloc_fd(dev_ion_fd, len, 0, ION_HEAP_DMA_RESERVED_MASK/*mask*/, 0x3, fd);
#else
	HDCP2_Log("PVV: ion_alloc_fd:  Other");
	ret = ion_alloc_fd(dev_ion_fd, len, 0, -1/*mask*/, TEEGRIS_ION_FLAG_FORCE_CONTIGUOUS | TEEGRIS_ION_FLAG_CACHED, fd);
#endif

	if (ret < 0) {
		HDCP2_Log("alloc_buf: ion_alloc_fd fail %s, len = %d", strerror(errno), len);
		goto err_alloc;
	}

err_alloc:
	close(dev_ion_fd);
	dev_ion_fd= -1;
	return ret;
}


#ifdef USE_SYSTEM_PARTITION
bool isAllZero(unsigned char* buf, int size)
{
	int i;

	if (buf == NULL) {
		HDCP2_Log("%s: buf is null\n", __func__);
		return false;
	}

	if (size < 0) {
		HDCP2_Log("%s: size is wrong\n", __func__);
		return false;
	}

	for (i = 0; i < size; i++) {
		if (buf[i] != 0x00) {
			return false;
		}
	}

	return true;
}
#endif /* USE_SYSTEM_PARTITION */

int alloc_ion_buffer(uint32_t length, ion_buffer_t *ion_buf)
{
	int ret = HDCP2_OK;
	int ion_fd_2_phys = 0;
	int num_pfns = 0;
	struct ionfd2phys ionfd2phys;

	if (length <= 0 || ion_buf == NULL) {
		HDCP2_Log("Invalid input in alloc_ion_buffer(). length:%x", length);
		return HDCP2_ERR_INVALID_INPUT;
	}

	ret = alloc_buf(length, &(ion_buf->fd));
	if (ret < 0) {
		HDCP2_Log("alloc_ion_buffer: _alloc_buf fail: %d", ret);
		ret = HDCP2_ERR;
		goto error;
	}

	ion_fd_2_phys = open("/dev/ionfd2phys", O_RDWR);
	if (ion_fd_2_phys < 0) {
		HDCP2_Log("alloc_ion_buffer: ion open fail with error code %d i.e %s. ", errno, strerror(errno));
		return HDCP2_ERR;
	}

	num_pfns = (length%PAGE_SIZE == 0) ? (length/PAGE_SIZE) : (length/PAGE_SIZE) + 1;
	allocated_buf_pfns = (unsigned long *)malloc(sizeof(unsigned long) *num_pfns);
	if (allocated_buf_pfns == NULL) {
		HDCP2_Log("Memory allocation failed\n");
		ret = HDCP2_ERR;
		goto error;
	}

	ionfd2phys.fd = ion_buf->fd;
	ionfd2phys.nr_pfns = num_pfns;
	ionfd2phys.pfns = allocated_buf_pfns;

	ret = ioctl(ion_fd_2_phys, 0, &ionfd2phys);
	if (ret) {
		HDCP2_Log("alloc_ion_buffer: get_phys_addr_from_ion fail,%d... %s", errno, strerror(errno));
		ret = HDCP2_ERR;
		goto error;
	}
	ion_buf->physaddr = (unsigned char*)ionfd2phys.pfns[0];

	ion_buf->viraddr = (u8*)mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, ion_buf->fd, 0);
	if (ion_buf->viraddr == MAP_FAILED) {
		HDCP2_Log("alloc_ion_buffer: mmap fail, fd:%d (len=%d), errno:%d, err: %s", ion_buf->fd, length, errno, strerror(errno));
		ret = HDCP2_ERR;
		goto error;
	}

	ion_buf->length = length;

error:
	close(ion_fd_2_phys);
	ion_fd_2_phys =-1;
	return ret;
}

/**
 * @fn static int _free_buf(HDCP2_Ctx *hdcp, secmem_buffer *buf)
 * @brief This function unmaps the buffer virtual address and closes fd.
 * @param hdcp - pointer to HDCP context
 * @param buf - buffer to be freed
 * @return int - returns HDCP2_OK else returns HDCP2_ERR_INVALID_INPUT.
 */
static int free_ion_buffer(ion_buffer_t *ion_buf)
{
	if (ion_buf != NULL) {
		munmap(ion_buf->viraddr, ion_buf->length);
		close(ion_buf->fd);
		ion_buf->fd = -1;
	}
	return HDCP2_OK;
}

#ifdef USE_MTK
int get_phys_addr_from_ion(int client_fd, uint8_t **physaddr)
{
	int ret = 0;
	int ion_fd = -1;
	ion_user_handle_t handle = 0;
	struct ion_sys_data sys_data;

	ion_fd = open("/dev/ion", O_RDONLY);
	if (ion_fd == -1) {
		HDCP2_Log("ion open fail with error code %d i.e %s. ",errno, strerror(errno));
		goto error;
	}

	ret = ion_import(ion_fd, client_fd, &handle);
	if (ret < 0) {
		HDCP2_Log("ion import fail with error code %d i.e %s. ",errno, strerror(errno));
		goto error;
	}
	memset(&sys_data, 0x00, sizeof(sys_data));
	sys_data.sys_cmd = ION_SYS_GET_PHYS;
	sys_data.get_phys_param.handle = handle;

	ret = ion_custom_ioctl(ion_fd, ION_CMD_SYSTEM, &sys_data);
	if (ret) {
		HDCP2_Log("ioctl[ION_ION_CUSTOM] Get phys failed!(ret %d, errono %d, i.e %s)", ret, errno, strerror(errno));
		ret = -1;
		goto error;
	}

	HDCP2_DEBUG("physical address : 0x%x(len: %d)", sys_data.get_phys_param.phy_addr, sys_data.get_phys_param.len);

	*physaddr = (unsigned char*)sys_data.get_phys_param.phy_addr;

error:
	if (ion_fd >= 0) {
		close(ion_fd);
		ion_fd = -1;
	}

	return ret;
}
#else
int get_phys_addr_from_ion(int fd, uint32_t length, uint8_t **physaddr)
{
	int ret = 0;
	int num_pfns = 0;
	struct ionfd2phys ionfd2phys;

	/* open ion device */
	ion_fd = open("/dev/ionfd2phys", O_RDONLY);

	if (ion_fd == -1) {
		HDCP2_Log("ion open fail with error code %d i.e %s. ",errno, strerror(errno));
		return HDCP2_ERR;
	}

	num_pfns = (length%PAGE_SIZE == 0) ? (length/PAGE_SIZE) : (length/PAGE_SIZE) + 1;
	buf_pfns = (unsigned long *)malloc(sizeof(unsigned long) *num_pfns);
	if (buf_pfns == NULL) {
		HDCP2_Log("Memory allocation failed\n");
		ret = HDCP2_ERR;
		goto error;
	}

	ionfd2phys.fd = fd;
	ionfd2phys.nr_pfns = num_pfns;
	ionfd2phys.pfns = buf_pfns;

	/* get ION fd_data.fd */

	HDCP2_DEBUG(" before pfns[0] : 0x%x", ionfd2phys.pfns[0]);

	ret = ioctl(ion_fd, 0, &ionfd2phys);
	if (ret) {
		HDCP2_Log("get_phys_addr_from_ion: get phys address fail,%d... %s",errno,strerror(errno));
		ret = HDCP2_ERR;
		goto error;
	}

	//HDCP2_Log("received fd : %d ; after pfns[0] : 0x%x", fd,ionfd2phys.pfns[0]);
	*physaddr = (unsigned char*)ionfd2phys.pfns[0];

error:
	close(ion_fd);
	ion_fd =-1;
	return ret;
}
#endif /* USE_MTK */

/**
 * @fn int HDCP2_HW_Init(HDCP2_Ctx *hdcp)
 * @brief This function calls TZ side to do HW Init (i.e open Crypto Dev)
 * @param hdcp - pointer to HDCP context
 * @return int - HDCP2_OK if success else returns corresponding error code.
 */
int HDCP2_HW_Init(HDCP2_Ctx *hdcp)
{
	int ret = HDCP2_OK;

	if (!hdcp) {
		HDCP2_Log("Invalid input in HDCP2_HW_Init(). hdcp:%x", hdcp);
		return HDCP2_ERR_INVALID_INPUT;
	}

	if (hdcp->entity == HDCP2_TRANSMITTER) {
		ret = TZ_COMMAND_T(CMD_TRUSTZONE_INIT, NULL, 0, NULL, 0);
	} else {
		ret = TZ_COMMAND_R(CMD_TRUSTZONE_INIT, NULL, 0, NULL, 0);
	}

	if (ret != HDCP2_OK) {
		HDCP2_Log("Cannot Init HW...");
	} else {
		hdcp->hwinit = 1;
	}

	return ret;
}

/**
 * @fn int HDCP2_HW_Close(HDCP2_Ctx *hdcp)
 * @brief This function calls TZ side to do HW Close (i.e close Crypto Dev)
 * @param hdcp - pointer to HDCP context.
 * @return int - HDCP2_OK in case of success, else error code corresponding to the error.
 */
int HDCP2_HW_Close(HDCP2_Ctx *hdcp)
{
	int ret = HDCP2_OK;

	if (!hdcp) {
		HDCP2_Log("Invalid input in HDCP2_HW_Close(). hdcp:%x", hdcp);
		return HDCP2_ERR_INVALID_INPUT;
	}

	if (!hdcp->hwinit)
		return HDCP2_OK;

	if (allocated_ion) {
		free_ion_buffer(&ion_buf);
		allocated_ion = 0;
	}

	/* free allocated_buf_pfns which are allocated during alloc_ion_buffer */
	if (allocated_buf_pfns != NULL) {
		free(allocated_buf_pfns);
		allocated_buf_pfns = NULL;
	}

	if (hdcp->entity == HDCP2_TRANSMITTER) {
		ret = TZ_COMMAND_T(CMD_TRUSTZONE_CLOSE, NULL, 0, NULL, 0);
	} else {
		ret = TZ_COMMAND_R(CMD_TRUSTZONE_CLOSE, NULL, 0, NULL, 0);
	}
	if (ret != HDCP2_OK)
		HDCP2_Log("HW Close Failed(%d)...Ignore", ret);

	hdcp->hwinit = 0;

	return HDCP2_OK;
}

/**
 * @fn int HDCP2_TZ_Open(HDCP2_Ctx *hdcp, const int entity)
 * @brief This function opens session with the Trustlet by acquiring the required resources
 * @param hdcp - pointer to HDCP context
 * @param entity - specifies if the devices is a transmitter or receiver or a repeater.
 * @return int - HDCP2_OK in case of success, else error code corresponding to the error.
 */
int HDCP2_TZ_Open(HDCP2_Ctx *hdcp, const int entity)
{
	TEEC_Result teec_ret = TEEC_SUCCESS;
	int  ret = HDCP2_OK;
#ifdef USE_SYSTEM_PARTITION
	uint8_t *ta_image = NULL;
	FILE *fp = NULL;
	size_t ta_image_size = 0;
#endif /* USE_SYSTEM_PARTITION */

	TEEC_UUID *uuid = {0};
	if (!hdcp)
		return HDCP2_ERR_INVALID_INPUT;

	// Close if previous session was not closed.
	if (ga_session.imp!=NULL) {
		HDCP2_Log("TZ_Open: Error, TZ is already opened");
		HDCP2_TZ_Close(hdcp);
		ret = HDCP2_ERR_HW_INIT;
		return ret;
	}

	// Initialize context
	teec_ret = TEEC_InitializeContext(NULL, &ga_context);
	if (TEEC_SUCCESS != teec_ret) {
		HDCP2_Log("TZ_Open: Error, TEEC Context Initialization failed with return value %x", teec_ret);
		ret = HDCP2_ERR_HW_INIT;
		return ret;
	}

#if !defined(USE_EXYNOS)
	teec_ret=TEECS_SetCryptoClk(&ga_context, 1/*ENABLE_CRYPTO_CLK*/);
	if (TEEC_SUCCESS != teec_ret) {
		HDCP2_Log("TEECS_SetCryptoClk, ENABLE_CRYPTO_CLK failed : %d", teec_ret);
		ret = HDCP2_ERR_HW_INIT;
		return ret;
	}
#endif /* !USE_EXYNOS */

	HDCP2_Log("after context initialzation");

#ifdef USE_SYSTEM_PARTITION
	if ((fp = fopen("/system/tee/00000000-0000-0000-0000-000048444350", "rb")) == NULL) {
		HDCP2_Log("Failed to open file(%s)\n", strerror(errno));
		ret = HDCP2_ERR_HW_INIT;
		return ret;
	}

	fseek(fp, 0, SEEK_END);
	ta_image_size = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	if (ta_image_size <= 0 || ta_image_size > 1024 * 1024 * 2) {
		HDCP2_Log("File size is zero\n");
		ret = HDCP2_ERR_HW_INIT;
		goto out;
	}

	ta_image = (uint8_t*)malloc(ta_image_size + 1);
	if (ta_image == NULL) {
		HDCP2_Log("Memory allocation failed\n");
		ret = HDCP2_ERR_HW_INIT;
		goto out;
	}

	memset(ta_image, 0, ta_image_size + 1);
	fread(ta_image, ta_image_size, 1, fp);

	if (isAllZero(ta_image, ta_image_size)) {
		HDCP2_Log("Unknown error : Failed to read ta image\n");
		ret = HDCP2_ERR_HW_INIT;
		goto out;
	}
#endif /* USE_SYSTEM_PARTITION */

	uuid =  &ga_hdcp2_transmitter_uuid;
	ga_operation.paramTypes = TEEC_PARAM_TYPES(TEEC_NONE,TEEC_NONE, TEEC_NONE, TEEC_NONE);

	//Open trustlet session
#ifdef USE_SYSTEM_PARTITION
	teec_ret = TEECS_OpenSession(&ga_context, &ga_session, uuid, ta_image, ta_image_size, 0, NULL, &ga_operation, &ga_returnOrigin);
#else
	teec_ret = TEEC_OpenSession(&ga_context, &ga_session, uuid, 0, NULL, &ga_operation, &ga_returnOrigin);
#endif /* USE_SYSTEM_PARTITION */

	if (TEEC_SUCCESS != teec_ret) {
		HDCP2_Log("TZ_Open: Error, TEEC Open Session failed with return value %x, return origin===%d", teec_ret, ga_returnOrigin);
		TEEC_FinalizeContext(&ga_context);
		if (ga_context.imp != NULL) {
			HDCP2_Log("TZ_Close: Error, Releasing context failed");
		}
		ret = HDCP2_ERR_HW_INIT;
		goto out;
	} else {
		HDCP2_Log("TZ_Open: TEEC HDCP Session creation Success");
	}

	//set opened
	hdcp->tlc_opened = 1;
	memcpy(&hdcp->ga_sessionHandle, &ga_session, sizeof(TEEC_Session));

	ga_sharedmem.size = MAX_VIDEO_ENCRYPT_BUFFER; // 1MB
	ga_sharedmem.flags = TEEC_MEM_INPUT|TEEC_MEM_OUTPUT;

	ga_sharedmem_res.size = MAX_VIDEO_ENCRYPT_BUFFER; // 1MB
	ga_sharedmem_res.flags = TEEC_MEM_INPUT|TEEC_MEM_OUTPUT;

	//allocating shared memory
	teec_ret = TEEC_AllocateSharedMemory(&ga_context, &ga_sharedmem);
	if (TEEC_SUCCESS != teec_ret) {
		HDCP2_Log("TZ_Open: 1 Error, allocating shared memory failed  with ret val =  %x", teec_ret);
		ret = HDCP2_ERR_HW_INIT;
		goto out;
	}

	teec_ret = TEEC_AllocateSharedMemory(&ga_context, &ga_sharedmem_res);
	if (TEEC_SUCCESS != teec_ret) {
		HDCP2_Log("TZ_Open: 2 Error, allocating shared memory failed with ret value = %x ", teec_ret);
		ret = HDCP2_ERR_HW_INIT;
		goto out;
	}

	if (entity != HDCP2_FACTORY) {
		/* HW_Init */
		ret = HDCP2_HW_Init(hdcp);
		if (ret != HDCP2_OK) {
			HDCP2_Log("Cannot init h/w");
			HDCP2_TZ_Close(hdcp);
		}
	}
out:
#ifdef USE_SYSTEM_PARTITION
	if (fp != NULL)
		fclose(fp);
	if (ta_image != NULL)
		free(ta_image);
#endif /* USE_SYSTEM_PARTITION */

	return ret;
}

void HDCP2_TZ_Close(HDCP2_Ctx *hdcp)
{
	TEEC_Result teec_ret = TEEC_SUCCESS;

#if !defined(USE_EXYNOS)
	teec_ret=TEECS_SetCryptoClk(&ga_context, 0/*DISABLE_CRYPTO_CLK*/);
	if (TEEC_SUCCESS != teec_ret) {
		HDCP2_Log("TEECS_SetCryptoClk, DISABLE_CRYPTO_CLK failed : %d", teec_ret);
		return;
	}
#endif /* !USE_EXYNOS */

	if (!hdcp) {
		HDCP2_Log("TZ_Close: Error, null context...");
		return;
	}

	if (ga_session.imp==NULL) {
		HDCP2_Log("TZ_Close: Error, TZ is not opened...");
		return;
	}

	if ((ga_sharedmem.buffer == NULL)) {
		HDCP2_Log("TZ_Close: Error, Shared memory not allocated...");
		return;
	}

	if (HDCP2_HW_Close(hdcp) != HDCP2_OK) {
		HDCP2_Log("HDCP2_TZ_Close: Error, HDCP2_HW_Close is failed");
	}

	//releasing shared memory
	TEEC_ReleaseSharedMemory(&ga_sharedmem);
	if (ga_sharedmem.buffer != NULL) {
		HDCP2_Log("TZ_Close: Error, Releasing shared memory failed");
		return;
	}

	if (ga_sharedmem_res.buffer != NULL) {
		TEEC_ReleaseSharedMemory(&ga_sharedmem_res);
		if (ga_sharedmem_res.buffer != NULL) {
			HDCP2_Log("TZ_Close: Error, Releasing shared memory failed");
			return;
		}
	}

	//closing session
	TEEC_CloseSession(&ga_session);

	if (ga_session.imp != NULL) {
		HDCP2_Log("TZ_Close: Error, Closing HDCP session failed");
		return;
	}

	//deleting context
	TEEC_FinalizeContext(&ga_context);

	if (ga_context.imp != NULL) {
		HDCP2_Log("TZ_Close: Error, Releasing context failed");
		return;
	}

	hdcp->tlc_opened = 0;
	return;
}

int HDCP2_Encrypt_VV(HDCP2_Ctx *hdcp, u32 str_ctr, u64 in_ctr, u8 *input,
                    int inlen, u8 *output, int type)
{
	int ret = 0;
	CIP_DATA_INFO data = {0};
#ifdef USE_MTK
	char flag[1] = {0};
#endif /* USE_MTK */

	if (inlen <= 0 || !hdcp || !input || !output || inlen > MAX_ENCRYPT_BUFFER) {
		HDCP2_Log("HDCP2_ERR_INVALID_INPUT (inlen = %d)", inlen);
		return HDCP2_ERR_INVALID_INPUT;
	}

	HDCP2_DEBUG("HDCP2_Encrypt_VV - Start(%d)", type);

	data.str_ctr = (uint32_t)str_ctr;
	data.inp_ctr = (uint64_t)(in_ctr);
	data.input =  (uint8_t *)(input);
	data.output =  (uint8_t *)(output);
	data.split_ctr = 0;
	data.length = inlen;
	data.offset = type;

#ifdef USE_MTK
	property_get("wlan.hdcp2.secbuf", flag, "0");
	if (flag[0] == '1')
		data.is_sec_buf = 1;
	else
		data.is_sec_buf = 0;
	HDCP2_DEBUG("data.is_sec_buf : %d", data.is_sec_buf);
#endif /* USE_MTK */

	HDCP2_DEBUG("HDCP2_Encrypt_Drm[T] - input : 0x%x, output : 0x%x, str_ctr : %u, in_ctr : %zu, type : %d",
			input, output, str_ctr, in_ctr, type);
	if ((ret = TZ_COMMAND_T(CMD_CIP_ENC_DATA, (uint8_t *)&data, sizeof(CIP_DATA_INFO), output, data.length)) < 0) {
		HDCP2_Log("Ignore Frame, l=%d, t=%d", data.length, type);
	} else {
		ret = 0;
	}

	return ret;
}

int HDCP2_Encrypt_PV(HDCP2_Ctx *hdcp, u32 str_ctr, u64 in_ctr, u8 *input,
                    int inlen, u8 *output, int type)
{
	TEEC_Result teec_ret = TEEC_SUCCESS;
	int ret = 0;
	int remain = inlen;
	int enclen = 0;
	int i = 0;
	CIP_DATA_INFO data = {0,};
	uint8_t *physaddr = NULL;
	u8 *out = output;
#ifdef USE_MTK
	char flag[1] = {0,};
#endif /* USE_MTK */

	HDCP2_DEBUG("HDCP2_Encrypt_PV - Start(%d)", type);

	if (inlen <= 0 || !hdcp || !input || !output) {
		HDCP2_Log("HDCP2_ERR_INVALID_INPUT (inlen = %d)", inlen);
		return HDCP2_ERR_INVALID_INPUT;
	}

	data.str_ctr = (uint32_t)str_ctr;
	data.inp_ctr = (uint64_t)(in_ctr);
	data.input =  (uint8_t *)(input);
	data.output =  (uint8_t *)(output);

#ifdef USE_MTK
	property_get("wlan.hdcp2.secbuf", flag, "0");
	if (flag[0] == '1')
		data.is_sec_buf = 1;
	else
		data.is_sec_buf = 0;
	HDCP2_DEBUG("data.is_sec_buf : %d", data.is_sec_buf);
	ret = get_phys_addr_from_ion((int)(intptr_t)data.input, &physaddr);
#else
	ret = get_phys_addr_from_ion((int)(intptr_t)data.input, inlen, &physaddr);
#endif /* USE_MTK */
	if (ret < 0) {
		HDCP2_Log("Failed to get phys addr from ion");
		return HDCP2_ERR;
	}
	data.offset = type;

	HDCP2_DEBUG("TEST input length=%d", inlen);

	// allocate ion buffer for output
	if (allocated_ion == 0) {
		ret = alloc_ion_buffer(MAX_VIDEO_ENCRYPT_BUFFER, &ion_buf);
		if (ret < 0) {
			HDCP2_Log("_alloc_buf fail: %d", ret);
			return HDCP2_ERR;
		}
		allocated_ion = 1;
	}
	data.output = ion_buf.physaddr;

	while (remain > 0) {
		enclen = (remain > MAX_ENCRYPT_BUFFER) ? MAX_ENCRYPT_BUFFER : remain;
		data.str_ctr = (u32) str_ctr;
		data.inp_ctr = (u64) (in_ctr + i * MAX_ENCRYPT_BUFFER_BL);

		HDCP2_DEBUG("input=%d, remain=%d, enclen=%d", inlen, remain, enclen);

		data.input = physaddr;
		data.length = enclen;
		data.split_ctr = i;

		HDCP2_DEBUG("HDCP2_Encrypt_Drm[T] - input : 0x%x, output : 0x%x, str_ctr : %u, in_ctr : %lld, type : %d", data.input, data.output, str_ctr, data.inp_ctr, type);
		HDCP2_DEBUG("HDCP2_Encrypt_Drm[T] - data.split_ctr : %d", data.split_ctr);

		ga_operation.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_PARTIAL_INOUT, TEEC_MEMREF_PARTIAL_INOUT, TEEC_NONE, TEEC_NONE);

		memcpy(ga_sharedmem.buffer, &data, sizeof(CIP_DATA_INFO));

		ga_operation.params[0].memref.parent = &ga_sharedmem;

		if (ga_sharedmem_res.buffer == NULL) {
			ga_operation.params[1].memref.parent = &ga_sharedmem;
		} else {
			ga_operation.params[1].memref.parent = &ga_sharedmem_res;
		}

		ga_operation.params[0].memref.size = sizeof(CIP_DATA_INFO); // req_size
		ga_operation.params[1].memref.size = data.length; //res_size
		ga_operation.params[0].memref.offset = 0;
		ga_operation.params[1].memref.offset = 0;

		teec_ret = TEEC_InvokeCommand(&ga_session, CMD_TRANSMITTER + CMD_CIP_ENC_DATA, &ga_operation, &ga_returnOrigin);
		if (teec_ret != TEEC_SUCCESS) {
			HDCP2_Log("TA_COMMAND: Error, TEEC_InvokeCommand failed with return error:%x , return origin:%d", teec_ret, ga_returnOrigin);
			ret = HDCP2_ERR;
			goto err;
		}

		/* copy encrypted buffer to wfd ouput buffer */
		memcpy((u8 *)(out + i * MAX_ENCRYPT_BUFFER), ion_buf.viraddr, enclen);
		i++;
		remain -= enclen;
	}

	/* free buf pfns which are allocated during get_phys_addr_from_ion */
	if (buf_pfns != NULL) {
		free(buf_pfns);
		buf_pfns = NULL;
	}

	ret = 0;
err:
	return ret;
}

int TA_COMMAND(HDCP2_Ctx *hdcp, const uint8_t command, uint8_t *request, uint32_t req_size,
		uint8_t *response, uint32_t res_size)
{
	TEEC_Result teec_ret = TEEC_SUCCESS;
	int  ret = HDCP2_OK;
	uint8_t ga_commandId = command;
	u8 dummy[HDCP2_BUFFER_LEN] = {0, };
	u8 *input = {0};
	u8 *output = {0};
	uint32_t inlen = 0;
	uint32_t outlen = 0;
	uint8_t *temp = NULL;
	CIP_DATA_INFO *tx_data = NULL;
	CIP_DATA_INFO_RX *rx_data = NULL;
	CIP_DATA_SPSPPS *spspps_data = NULL;
	uint8_t *physaddr = NULL;
	u8 isVideo = 0;
	u8 isSpsPps = 0;
	HDCP2_DEBUG("TA_COMMAND: [Command ID: %d]",ga_commandId);

	if (!hdcp || !ga_sharedmem.buffer || req_size > TEEC_CONFIG_SHAREDMEM_MAX_SIZE) {
		HDCP2_Log("TA_COMMAND: Error, Invalid parameter");
		ret = HDCP2_ERR_INVALID_INPUT;
		goto err;
	}

	if (!hdcp->tlc_opened) {
		HDCP2_Log("TA_COMMAND : Error, TA is not opened");
		ret = HDCP2_ERR;
		goto err;
	}

	ga_operation.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_PARTIAL_INOUT, TEEC_MEMREF_PARTIAL_INOUT, TEEC_NONE, TEEC_NONE);
	input = (request) ? request : dummy;
	output = (response) ? response : dummy;
	inlen = (request) ? req_size : HDCP2_BUFFER_LEN;
	outlen = (response) ? res_size : HDCP2_BUFFER_LEN;
	outlen = res_size;

	memcpy(ga_sharedmem.buffer, input, inlen);

	if (ga_commandId == CMD_TRANSMITTER + CMD_CIP_ENC_DATA) {
		tx_data = (CIP_DATA_INFO*)input;
		if (tx_data->offset == -1) {
			temp = (uint8_t*)ga_sharedmem.buffer;
			memcpy(temp+inlen, tx_data->input, tx_data->length);
			inlen += tx_data->length;
		}
	} else if (ga_commandId == CMD_RECEIVER + CMD_CIP_DEC_DATA) {
		rx_data = (CIP_DATA_INFO_RX*)ga_sharedmem.buffer;
		if (rx_data->dec_type == DEC_TYPE_VIDEO) {
			// get phys address from output ion buffer
#ifdef USE_MTK
			get_phys_addr_from_ion((int)(intptr_t)rx_data->output, &physaddr);
#else
			get_phys_addr_from_ion((int)(intptr_t)rx_data->output, rx_data->length, &physaddr);
#endif /* USE_MTK */

			//rx_data = (CIP_DATA_INFO_RX*)ga_sharedmem.buffer;
			rx_data->output = physaddr;
			isVideo = 1;

			// allocate ion buffer for input
			if (allocated_ion == 0) {
				ret = alloc_ion_buffer(MAX_VIDEO_ENCRYPT_BUFFER, &ion_buf);
				if (ret < 0) {
					HDCP2_Log("_alloc_buf fail: %d", ret);
					ret = HDCP2_ERR;
					goto err;
				}
				allocated_ion = 1;
			}
			memcpy(ion_buf.viraddr, rx_data->input, rx_data->length);
			rx_data->input = ion_buf.physaddr;
		} else {
			rx_data = (CIP_DATA_INFO_RX*)input;
			temp = (uint8_t*)ga_sharedmem.buffer;
			memcpy(temp+inlen, rx_data->input, rx_data->length);
			inlen += rx_data->length;
		}
	} else if (ga_commandId == CMD_RECEIVER + CMD_CIP_SPS_PPS_DATA) {
		spspps_data = (CIP_DATA_SPSPPS*)ga_sharedmem.buffer;
		// get phys address from output ion buffer (spspps_data->phy_out is ion buffer here)
#ifdef USE_MTK
		get_phys_addr_from_ion((int)(intptr_t)spspps_data->phy_out, &physaddr);
#else
		get_phys_addr_from_ion((int)(intptr_t)spspps_data->phy_out, spspps_data->size, &physaddr);
#endif /* USE_MTK */

		spspps_data->phy_out = physaddr;
		isSpsPps = 1;
	}

	ga_operation.params[0].memref.parent = &ga_sharedmem;

	if (ga_sharedmem_res.buffer == NULL) {
		ga_operation.params[1].memref.parent = &ga_sharedmem;
	} else {
		ga_operation.params[1].memref.parent = &ga_sharedmem_res;
	}

	ga_operation.params[0].memref.size = inlen; // req_size
	ga_operation.params[1].memref.size = outlen; //res_size
	ga_operation.params[0].memref.offset = 0;
	ga_operation.params[1].memref.offset = 0;

	teec_ret = TEEC_InvokeCommand(&ga_session, (uint32_t)ga_commandId, &ga_operation, &ga_returnOrigin);

	if (teec_ret != TEEC_SUCCESS) {
		HDCP2_Log("TA_COMMAND: Error, TEEC_InvokeCommand failed with return error:%x , return origin:%d", teec_ret, ga_returnOrigin);
		ret = HDCP2_ERR;
		goto err;
	}

	if (ga_operation.params[1].memref.size && isVideo == 0 && isSpsPps == 0)
		memcpy(response, ga_operation.params[1].memref.parent->buffer, ga_operation.params[1].memref.size);

	if (ga_commandId == CMD_TRANSMITTER + CMD_CIP_ENC_DATA)
		return tx_data->length;

	ret = ga_operation.params[1].memref.size;

err:
	return ret;
}

int HDCP2_Decrypt_Ex(HDCP2_Ctx *hdcp, u32 str_ctr, u64 in_ctr, u8 *input, int inlen, u8 *output, u32 dec_type, int codec_type)
{
	int ret = 0;
	CIP_DATA_INFO_RX data = {0};

	if (inlen < 0 || !hdcp || !input || !output || inlen > MAX_ENCRYPT_BUFFER) {
		HDCP2_Log("HDCP2_ERR_INVALID_INPUT (inlen = %d)", inlen);
		return HDCP2_ERR_INVALID_INPUT;
	}

	data.str_ctr = (u32)str_ctr;
	data.inp_ctr = (u64)in_ctr;

	HDCP2_DEBUG("HDCP2_Decrypt_Ex: input=%d, in_ctr=%d, str_ctr=%d", inlen, in_ctr, str_ctr);

	data.input = input;
	data.output = output;
	data.length = inlen;
	data.dec_type = dec_type;
	data.codec_type = codec_type;

	if ((ret = TZ_COMMAND_R(CMD_CIP_DEC_DATA, (u8 *)&data, sizeof(CIP_DATA_INFO_RX), data.output, data.length)) < 0) {
		HDCP2_Log("Re-Decrypt Frame");
		if ((ret = TZ_COMMAND_R(CMD_CIP_DEC_DATA, (u8 *)&data, sizeof(CIP_DATA_INFO_RX), data.output, data.length)) < 0) {
			HDCP2_Log("Ignore Frame, l=%d", data.length);
		}
		ret = HDCP2_OK;
	}

	return ret;
}

int HDCP2_Decrypt_Ex_IsKeyFrame(HDCP2_Ctx *hdcp, u32 str_ctr, u64 in_str,
		u8 *input, int inlen, u8 *output, u32 dec_type, int codec_type)
{
	int ret = 0;
	CIP_DATA_INFO_RX data = {0};

	if (inlen < 0 || !hdcp || !input || !output || inlen > MAX_ENCRYPT_BUFFER) {
		HDCP2_Log("HDCP2_ERR_INVALID_INPUT (inlen = %d)", inlen);
		return HDCP2_ERR_INVALID_INPUT;
	}

	data.str_ctr = (u32)str_ctr;
	data.inp_ctr = (u64)in_str;

	HDCP2_DEBUG("HDCP2_Decrypt_Ex: input=%d, in_str=%d, str_ctr=%d", inlen, in_str, str_ctr);

	data.input = (u8 *)(input);
	data.output = (u8 *)(output);
	data.length = inlen;
	data.dec_type = dec_type;
	data.codec_type = codec_type;

	if ((ret = TZ_COMMAND_R(CMD_CIP_DEC_DATA, (u8 *)&data, sizeof(CIP_DATA_INFO_RX), data.output, data.length)) < 0) {
		HDCP2_Log("Re-Decrypt Frame");
		if ((ret = TZ_COMMAND_R(CMD_CIP_DEC_DATA, (u8 *)&data, sizeof(CIP_DATA_INFO_RX), data.output, data.length)) < 0) {
			HDCP2_Log("Ignore Frame, l=%d", data.length);
		}
		ret = HDCP2_OK;
	}

	return ret;
}

int HDCP2_Decrypt_Ex_Audio(HDCP2_Ctx *hdcp, u32 str_ctr, u64 in_str, u8 *input,
		int inlen, u8 *output, u32 dec_type, int codec_type)
{
	int ret = 0;

	if (!hdcp)
		return HDCP2_ERR_INVALID_INPUT;

	ret = HDCP2_Decrypt_Ex(hdcp, str_ctr, in_str, input, inlen, output, DEC_TYPE_AUDIO, 0);
	HDCP2_DEBUG("HDCP2_DecryptAudio", output, ret);

	return ret;
}

int HDCP2_Decrypt_Ex_VP(HDCP2_Ctx *hdcp, u32 str_ctr, u64 in_str, u8 *input,
		int inlen, u8 *output, u32 dec_type, int codec_type)
{
	return 0;
}

int HDCP2_Decrypt_ION(HDCP2_Ctx *hdcp, u32 str_ctr, u64 in_str, u8 *input, int inlen, u32 ion_fd)
{
	int ret = 0;
	CIP_DATA_INFO_RX data = {0};

	if (inlen < 0 || !hdcp || !input || inlen > MAX_ENCRYPT_BUFFER) {
		HDCP2_Log("HDCP2_ERR_INVALID_INPUT (inlen = %d)", inlen);
		return HDCP2_ERR_INVALID_INPUT;
	}

	HDCP2_DEBUG("HDCP2_Decrypt_ION: input=0x%.8x, ion_fd=%d inlen=%d", input, ion_fd, inlen);

	data.str_ctr = (u32)str_ctr;
	data.inp_ctr = (u64)(in_str);

	data.input = (u8 *)(input);
	data.output = (u8 *)(uintptr_t)ion_fd;
	data.length = inlen;
	data.dec_type = DEC_TYPE_VIDEO;

	if ((ret = TZ_COMMAND_R(CMD_CIP_DEC_DATA, (u8 *)&data, sizeof(CIP_DATA_INFO_RX), NULL, 0)) < 0) {
		HDCP2_Log("Re-Decrypt Frame");
		if ((ret = TZ_COMMAND_R(CMD_CIP_DEC_DATA, (u8 *)&data, sizeof(CIP_DATA_INFO_RX), NULL, 0)) < 0) {
			HDCP2_Log("Ignore Frame, l=%d", data.length);
		}
		ret = HDCP2_OK;
	}

	HDCP2_DEBUG("HDCP2_Decrypt_ION: inlen %d outlen %d", inlen, ret);

	return ret;
}

void HDCP2_TZ_Release_Resmem()
{
	TEEC_ReleaseSharedMemory(&ga_sharedmem_res);
	if (ga_sharedmem_res.buffer != NULL) {
		HDCP2_Log("TZ_Close: Error, Releasing shared Res memory failed");
		return;
	}

	HDCP2_Log("HDCP2_TZ_Release_Resmem : Releasing shared Res Memory");

	return;
}
