#include "ta_driver.h"
#include <driver/mem/phys.h>
#include <tee_internal_api.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <driver.h>
#include <sys/ioctl.h>
#include <core/access.h>
#include <core/driver.h>
#ifdef SEC_SDK50
#include <core/uio.h>
#endif
#include "tima_util.h"
#include "tima_ioctl.h"

#include "allowlist.h"

#include "drTimaMm.h"
#include "tima_mem_address.h"
#include <stdbool.h>
#include <uuid/uuid.h>

#define TAG                     "DRIVER_DBG: "
#define DEBUG
#define UUID_LENGTH 16
#define _ROUND_PGDOWN(n)         (((uintptr_t)(n)) & PAGE_MASK)
#define ROUND_PGUP(n)            (_ROUND_PGDOWN((n) + (PAGE_SIZE - 1)))
#define ROUND_PGDOWN(n)          (_ROUND_PGDOWN(n))

#ifdef DEBUG
#define print_info(format, args...)     printf(TAG format, ##args)
#else
#define print_info(format, args...)
#endif

log_info_t sec_log_info, dbg_log_info;
uint8_t tima_logging_init = -1;

static struct usr_drv_info *drv_info = NULL;

static const TEEC_UUID uuid = TA_PROP_UUID;

struct fops file_ops = {0};

static int drv_open(struct drv_info *info, const char *drv_path)
{
    int ret = 0;

    (void) info;
    (void) drv_path;


    return ret;
}

#ifdef SEC_SDK50
static int drv_ioctl_new(struct drv_info *info, int ioctl_cmd, struct ioctl_arg *ioctl_data)
{
    uint32_t hasPermission = in_uuid_allowlist(&info->uuid);
    if(hasPermission != READ_WRITE_PERMISSION && hasPermission != READ_PERMISSION) {
        printf(TAG "\nTA is not in TIMA_ALLOWLIST");
        return -1;
    }
    struct ioctl_mem_access_data *ma_data = NULL;
    size_t buf_size;
    char *temp;
    int ret = 0;
    TEE_UUID *drv_allowlist = NULL;
    switch(ioctl_cmd) {
        case TIMAUTIL_SECURE_PHYS_READ:
            if((hasPermission != READ_WRITE_PERMISSION) && (hasPermission != READ_PERMISSION)) {
                printf(TAG "TA doesn't have read permission %d\n",hasPermission);
                return -1;
            }
            ma_data = (struct ioctl_mem_access_data *)ioctl_data->input[0].iov_base;

            if (ioctl_data->input[0].iov_len != sizeof(struct ioctl_mem_access_data)) {
                return -EINVAL;
            }

            if(ma_data == NULL) {
                return -1;
            }
            temp = (char *)ioctl_data->input[1].iov_base;

            if(temp == NULL) {
                return -1;
            }
            timautil_secure_phys_read(ma_data, temp);
            break;

        case TIMAUTIL_SECURE_PHYS_WRITE:

            if((hasPermission != READ_WRITE_PERMISSION) && (hasPermission != READ_PERMISSION)) {
                return -1;
            }
            ma_data = (struct ioctl_mem_access_data *)ioctl_data->input[0].iov_base;

            if (ioctl_data->input[0].iov_len != sizeof(struct ioctl_mem_access_data)) {
                return -EINVAL;
            }

            if(ma_data == NULL) {
                return -1;
            }
            temp = (char *)ioctl_data->input[1].iov_base;

            if(temp == NULL) {
                return -1;
            }
            tima_util_secure_phys_write(ma_data, temp);
            break;
        default :
            break;
    }
    return TEE_SUCCESS;
}
#endif

static int drv_ioctl(struct drv_info *info, int ioctl_cmd, unsigned long ioctl_data)
{
#ifdef SEC_SDK50
        return drv_ioctl_new(info,ioctl_cmd,ioctl_data);
#else
    uint32_t hasPermission = in_uuid_allowlist(&info->uuid);
    if(hasPermission != READ_WRITE_PERMISSION && hasPermission != READ_PERMISSION) {
        printf(TAG "\nTA is not in TIMA_ALLOWLIST");
        return -1;
    }
    struct ioctl_log_data *log_data = NULL;
    struct ioctl_mem_access_data *ma_data = NULL;
    uint32_t paramTypes = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INOUT,TEE_PARAM_TYPE_NONE,TEE_PARAM_TYPE_NONE,TEE_PARAM_TYPE_NONE);
    char *temp;
    uint32_t buf_size;

    bool is_cncc = false;

    TEE_UUID *drv_allowlist = NULL;
    switch(ioctl_cmd) {
        case TIMAUTIL_SECURE_ALLOWLIST :
            is_cncc = check_cncc_uuid(&info->uuid);
            if(!is_cncc) return -1;
            drv_allowlist = TEES_AcquireUserBuffer(info, (uint64_t)ioctl_data,
                            (ROUND_PGUP(ioctl_data) - ioctl_data), PROT_READ);

            if(drv_allowlist == NULL) {
                printf(TAG "\nIn TIMAUTIL_SECURE_ALLOWLIST: Acquiring drv_allowlist failed");
                return -1;
            }
            process_update_allowlist(drv_allowlist);
            break;
        case TIMAUTIL_DEBUG_LOG_WRITE :
#if defined (CONFIG_TIMA_LOG)
            buf_size = sizeof(struct ioctl_log_data);
            log_data = (struct ioctl_log_data *)TEES_AcquireUserBuffer(info, 
                       ioctl_data, buf_size, PROT_READ);

            if(log_data == NULL) {
                printf(TAG "\nIn TIMAUTIL_DEBUG_LOG_WRITE: Acquiring log_data failed");
                return -1;
            }
            log_msg(dbg_log_info, log_data->buf);
            TEES_ReleaseUserBuffer(log_data, buf_size);
#endif
            break;
        case TIMAUTIL_SECURE_LOG_WRITE :
#if defined (CONFIG_TIMA_LOG)
            buf_size = sizeof(struct ioctl_log_data);			
            log_data = (struct ioctl_log_data *)TEES_AcquireUserBuffer(info, 
                       ioctl_data, buf_size, PROT_READ);

            if(log_data == NULL) {
                printf(TAG "\nIn TIMAUTIL_SECURE_LOG_WRITE: Acquiring log_data failed");
                return -1;
            }
            log_msg(sec_log_info, (char*)log_data->buf);
            TEES_ReleaseUserBuffer(log_data, buf_size);
#endif
            break;
        case TIMAUTIL_SECURE_PHYS_READ:
            if((hasPermission != READ_WRITE_PERMISSION) && (hasPermission != READ_PERMISSION)) {
                printf(TAG "TA doesn't have read permission %d\n",hasPermission);
                return -1;
            }
            buf_size = sizeof(struct ioctl_mem_access_data);
            ma_data = (struct ioctl_mem_access_data *)TEES_AcquireUserBuffer(info,
                      ioctl_data, buf_size, PROT_READ | PROT_WRITE);

            if(ma_data == NULL) {
                printf(TAG "\nIn TIMAUTIL_SECURE_PHYS_READ: Acquiring ma_data failed");
                return -1;
            }
            temp = (char *)TEES_AcquireUserBuffer(info,
                               (uint32_t)ma_data->rw_addr, ma_data->len, PROT_READ | PROT_WRITE);

            if(temp == NULL) {
                printf(TAG "\nIn TIMAUTIL_SECURE_PHYS_READ: Acquiring ma_data->rw_addr failed");
                return -1;
            }
            timautil_secure_phys_read(ma_data, temp);
            TEES_ReleaseUserBuffer(temp, ma_data->len);
            TEES_ReleaseUserBuffer(ma_data, buf_size);
            break;
        case TIMAUTIL_SECURE_PHYS_WRITE:
            if(hasPermission != READ_WRITE_PERMISSION) {
                printf(TAG "TA doesn't have Write permission %d\n",hasPermission);
                return -1;
            }

            buf_size = sizeof(struct ioctl_mem_access_data);
            ma_data = (struct ioctl_mem_access_data *)TEES_AcquireUserBuffer(info,
                      ioctl_data, buf_size, PROT_READ | PROT_WRITE);

            if(ma_data == NULL) {
                printf(TAG "\nIn TIMAUTIL_SECURE_PHYS_WRITE: Acquiring ma_data failed");
                return -1;
            }
			
            temp = (char *)TEES_AcquireUserBuffer(info,
                               (uint32_t)ma_data->rw_addr, ma_data->len, PROT_READ);

            if(temp == NULL) {
                printf(TAG "\nIn TIMAUTIL_SECURE_PHYS_WRITE: Acquiring ma_data->rw_addr failed");
                return -1;
            }
            tima_util_secure_phys_write(ma_data, temp);
            TEES_ReleaseUserBuffer(temp, ma_data->len);
            TEES_ReleaseUserBuffer(ma_data, buf_size);
            break;
        case DMV_SRAM_READ_RECOVERY:
            //printf("SRAM Read recovery \n");
            buf_size = sizeof(struct ioctl_mem_access_data);
            ma_data = (struct ioctl_mem_access_data *)TEES_AcquireUserBuffer(info,
                      ioctl_data, buf_size, PROT_READ);
            //printf("Check 1\n");

            if(ma_data == NULL) {
                printf(TAG "\nIn DMV_SRAM_READ_RECOVERY: Acquiring ma_data failed");
                return -1;
            }

            //printf("Check 2\n");
             temp = (char *)TEES_AcquireUserBuffer(info,
                                (uint32_t)ma_data->rw_addr, ma_data->len, PROT_READ | PROT_WRITE);

             if(temp == NULL) {
                 printf(TAG "\nIn DMV_SRAM_READ_RECOVERY: Acquiring ma_data->rw_addr failed");
                 return -1;
             }

             printf("Calling SRAM recovery read \n");
             timautil_sram_recovery_read(ma_data, temp);
             TEES_ReleaseUserBuffer(temp, ma_data->len);
             TEES_ReleaseUserBuffer(ma_data, buf_size);
             break;
        default :
            printf("Bad command id in driver. command id is %x\n",ioctl_cmd);
            break;
    }
#endif
    return TEE_SUCCESS;
}


TEE_Result TA_CreateEntryPoint(void)
{
    int ret = 0;
    file_ops.open   = (void *)drv_open;
    //file_ops.write = drv_write;
#ifdef SEC_SDK50
        file_ops.ioctl_iov = drv_ioctl;
#else
        file_ops.ioctl  = drv_ioctl;
#endif
#if defined(SEC_SDK40)
    ret = TEES_InitDriver(drv_name_init, &file_ops, ACC_PERM_SEC_DRV, &drv_info);
#elif defined(SEC_SDK30)
    ret = TEES_InitDriver(drv_name, &file_ops, ACC_PERM_SEC_DRV, &drv_info);
#else
    ret = TEES_RegisterDriver(drv_name, &file_ops, ACC_PERM_SEC_DRV, &drv_info);
#endif
    if (ret) {
        printf("register_driver failed, ret = %d\n", ret);
        return TEE_ERROR_GENERIC;
    }
    printf("TIMA Driver registered\n");
    return TEE_SUCCESS;
}


void TA_DestroyEntryPoint(void)
{
#if defined(SEC_SDK30) || defined(SEC_SDK40)
    int ret = TEES_FiniDriver(drv_info);
#else
    int ret = TEES_ReleaseDriver(&drv_info);
#endif
    printf("ta_driver: drv_destroy\n");

    if (ret) {
        printf("release_driver failed, ret = %d\n'", ret);
    }
    printf("TIMA Driver released\n");
}

TEE_Result TA_OpenSessionEntryPoint(uint32_t paramTypes,
    TEE_Param params[4], void **sessionContext)
{
#if defined (CONFIG_TIMA_LOG)
    if (TEE_SUCCESS != tima_logging_init) {
        uint32_t drRet = 0;
        sec_log_info.log_start_paddr = TIMA_SEC_LOG_PADDR;
        sec_log_info.log_size = TIMA_SEC_LOG_SIZE;
        sec_log_info.log_hdr_vaddr = TIMA_SEC_LOG_VADDR;
        sec_log_info.log_data_vaddr = TIMA_SEC_LOG_PAGE;
        /* Initialize sec_log memory */
        drRet = drApiMapPhys((void *)TIMA_SEC_LOG_VADDR, TIMA_LOG_MAP_SIZE,
                            (void *)TIMA_SEC_LOG_PADDR);
        if (TEE_SUCCESS != drRet) {
            printf("TA_DRIVER: Cannot map sec_log hdr! SecLog not mapped! Driver Session Fail\n");
            drApiUnmap((void *)drRet,TIMA_LOG_MAP_SIZE);
            return TEE_ERROR_ACCESS_CONFLICT;
        }
        log_init(sec_log_info);
        dbg_log_info.log_start_paddr = TIMA_DBG_LOG_PADDR;
        dbg_log_info.log_size = TIMA_DBG_LOG_SIZE;
        dbg_log_info.log_hdr_vaddr = TIMA_DBG_LOG_VADDR;
        dbg_log_info.log_data_vaddr = TIMA_DBG_LOG_PAGE;
        printf("TA_OpenSessionEntryPoint: log_start_paddr is %d\n", dbg_log_info.log_start_paddr);
        /* Initialize debug_log memory */
        drRet = drApiMapPhys((void *)TIMA_DBG_LOG_VADDR, TIMA_LOG_MAP_SIZE,
                            (void *)TIMA_DBG_LOG_PADDR);
        if (TEE_SUCCESS != drRet) {
            printf("TA_DRIVER: Cannot map dbg_log hdr! DbgLog not mapped! Driver Session Fail\n");
            drApiUnmap((void *)drRet,TIMA_LOG_MAP_SIZE);
            return TEE_ERROR_ACCESS_CONFLICT;
        }
        log_init(dbg_log_info);
        tima_logging_init = TEE_SUCCESS;
        printf("TIMA LOGS open session success \n");
    }
    else
        printf("TIMA LOGS session already open\n");
#endif
    return TEE_SUCCESS;
}

void TA_CloseSessionEntryPoint(void *sessionContext)
{
    printf("ta_driver: closeSession\n");
}

TEE_Result TA_InvokeCommandEntryPoint(void *sessionContext,
    uint32_t commandID, uint32_t paramTypes, TEE_Param params[4])
{
    printf("ta_driver: drv_invoke\n");
    return TEE_SUCCESS;
}

