/*
 *
 * Copyright (C) 2012-2019, Samsung Electronics Co., Ltd.
 *
 * Macros and structure to map physical memory
 */

#include <tee_internal_api.h>
#include <core/page.h>
#include <driver/mem/phys.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "dbg.h"
#include "secmap.h"

#define PHYS_DEV_NAME	"/dev/phys"

typedef struct secmap_item {
    unsigned long phyaddr;
    void *vaddr;
    size_t size;
    int ref_count;
    struct secmap_item *next;
} secmap_item_t;

static int phys_fd = -1;
static secmap_item_t *sec_list_head;
static void *virt_addr[MAX_DEVICE_NUM];

/**
* Get virtual mapped address for secure device
* @dev_id  : Device identifier
*
* @return : Virtual address as void*
*/
void *get_dev_addr(devicenum_t dev_id)
{
    if (dev_id < MAX_DEVICE_NUM) {
        return virt_addr[dev_id];
    }
    return NULL;
}

static bool is_intersection(unsigned long addr1, size_t size1, unsigned long addr2, size_t size2)
{
    if ((addr1 + size1) <= addr2 || (addr2 + size2) <= addr1) {
        return false;
    }
    return true;
}

/**
 * Map secure device to physical base address
 * @dev_id  : Device identifier
 * @phyaddr : Mapping physical address
 * @size    : SFR region size
 *
 * @return : Virtual address as void* on success, NULL if error
 */
static void *sec_map_internal(devicenum_t dev_id, unsigned long phyaddr, size_t size)
{
    void *vaddr;
    secmap_item_t *item;
    uint32_t offset = phyaddr & ~PAGE_MASK;
    int flags = MAP_SHARED;

#ifndef USE_NON_CACHED_FB
    if (dev_id != DEV_TUI_WB && dev_id != DEV_TUI_FB) {
        flags |= MAP_PHYS_NON_CACHED;
    }
#else
    if (dev_id != DEV_TUI_WB) {
        flags |= MAP_PHYS_NON_CACHED;
    }
#endif

    item = sec_list_head;
    while(item) {
        if (item->phyaddr == phyaddr) {
            if(item->size != size) {
                errPrintf("check size parameter (%lx, %#zx), %#zx\n",
                          phyaddr, size, item->size);
                return NULL;
            }
            vaddr = item->vaddr;
            item->ref_count++;
            return vaddr;
        } else if (is_intersection(phyaddr, size, item->phyaddr, item->size)) {
            errPrintf("regions should not overlap (%lx, %#zx), (%lx, %#zx)\n",
                      phyaddr, size, item->phyaddr, item->size);
            return NULL;
        }
        item = item->next;
    }
    if (phys_fd < 0) {
        phys_fd = open(PHYS_DEV_NAME, O_RDWR);
        if (phys_fd == -1) {
            errPrintf("open() failed, err = %d \n", errno);
            return NULL;
        }
    }

    vaddr = mmap(NULL, size, PROT_READ | PROT_WRITE, flags,
                 phys_fd, phyaddr & PAGE_MASK);
    if (vaddr == MAP_FAILED) {
        errPrintf("mmap FAILED\n");
        return NULL;
    }
    vaddr = (void *)(((unsigned long)vaddr) | offset);
    item = TEE_Malloc(sizeof(secmap_item_t), 0);
    if (!item) {
        errPrintf("TEE_Malloc() FAILED\n");
        if (munmap((void *)vaddr, size)) {
            errPrintf("unmap failed with error : %d\n", errno);
        }
        return NULL;
    }
    item->phyaddr = phyaddr;
    item->vaddr = vaddr;
    item->size = size;
    item->ref_count = 1;
    item->next = sec_list_head;
    sec_list_head = item;
    return vaddr;
}

/**
 * Map secure device to physical base address without Device identifier
 * @phyaddr : Mapping physical address
 * @size    : SFR region size
 *
 * @return : Virtual address as void* on success, NULL if error
 */
void *sec_map_no_devid(unsigned long phyaddr, size_t size)
{
    return sec_map_internal(DEV_TUI_NONE, phyaddr, size);
}

/**
 * Map secure device to physical base address
 * @dev_id  : Device identifier
 * @phyaddr : Mapping physical address
 * @size    : SFR region size
 *
 * @return : 0 on success, -1 if error
 */
int sec_map(devicenum_t dev_id, unsigned long phyaddr, size_t size)
{
    void *vaddr;
    if (dev_id >= MAX_DEVICE_NUM) {
        return -1;
    }

    dbgPrintf("[%s] phys_addr=0x%lx, 0x%lx\n",__func__, phyaddr, size);

    vaddr = sec_map_internal(dev_id, phyaddr, size);
    if (vaddr == NULL) {
        virt_addr[dev_id] = NULL;
        errPrintf("sec_map_internal FAILED\n");
        return -1;
    }
    virt_addr[dev_id] = vaddr;
    return 0;
}

/**
 * Secure virtual address unmapping
 * @virtaddr : Unmapping virtual address
 *
 * @return : 0 on success, -1 if error
 */
int sec_unmap(void *virtaddr)
{
    int i;
    secmap_item_t *prev_item = NULL, *item = sec_list_head;
    if (virtaddr) {
        while(item) {
            if (item->vaddr == virtaddr) {
                if (item->ref_count == 1) {
                    if (munmap((void *)(((unsigned long)virtaddr) & PAGE_MASK), item->size)) {
                        errPrintf("unmap failed with error : %d\n", errno);
                    }
                    if (prev_item) {
                        prev_item->next = item->next;
                    } else {
                        sec_list_head = item->next;
                    }
                    TEE_Free(item);
                    return 0;
                } else if (item->ref_count > 1) {
                    item->ref_count--;
                    return 0;
                } else {
                    errPrintf("ref_count = %d\n", item->ref_count);
                    return -1;
                }
            }
            prev_item = item;
            item = item->next;
        }
        for(i = 0; i < MAX_DEVICE_NUM; i++) {
            if (virt_addr[i] == virtaddr) {
                virt_addr[i] = NULL;
            }
        }
    }
    /* errPrintf("Secunmap: FAILED, virt=0x%08x\n", (uint32_t)virtaddr); */
    return -1;
}

/**
 * Release all secure address mappings
 *
 * @return : None
 */
void sec_release(void)
{
    secmap_item_t  *next_item = NULL, *item = sec_list_head;

    if (phys_fd < 0) {
        return;
    }

    while(item) {
        next_item = item->next;
        if (item->vaddr) {
            if (munmap((void *)(((unsigned long)item->vaddr) & PAGE_MASK), item->size)) {
                errPrintf("unmap failed with error : %d\n", errno);
            }
        }
        TEE_Free(item);
        item = next_item;
    }
    sec_list_head = NULL;
    TEE_MemFill(virt_addr, 0, sizeof(virt_addr));
    if (close(phys_fd)) {
        errPrintf("close failed with error : %d\n", errno);
    }
    phys_fd = -1;
}