/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <trusty/trusty_dev.h>
#include <trusty/util.h>

/* 48-bit physical address bits 47:12 */

#define NS_PTE_PHYSADDR_SHIFT 12
#define NS_PTE_PHYSADDR(pte) ((pte)&0xFFFFFFFFF000ULL)

/* Access permissions bits 7:6
 *      EL0     EL1
 * 00   None    RW
 * 01   RW      RW
 * 10   None    RO
 * 11   RO      RO
 */
#define NS_PTE_AP_SHIFT 6
#define NS_PTE_AP_MASK (0x3 << NS_PTE_AP_SHIFT)

/* Memory type and cache attributes bits 55:48 */
#define NS_PTE_MAIR_SHIFT 48
#define NS_PTE_MAIR_MASK (0x00FFULL << NS_PTE_MAIR_SHIFT)

#define NS_PTE_MAIR_INNER_SHIFT 48
#define NS_PTE_MAIR_INNER_MASK (0x000FULL << NS_PTE_MAIR_INNER_SHIFT)

#define NS_PTE_MAIR_OUTER_SHIFT 52
#define NS_PTE_MAIR_OUTER_MASK (0x000FULL << NS_PTE_MAIR_OUTER_SHIFT)

/* Normal memory */

/* inner and outer write back read/write allocate */
#define NS_MAIR_NORMAL_CACHED_WB_RWA 0xFF
/* inner and outer write through read allocate */
#define NS_MAIR_NORMAL_CACHED_WT_RA 0xAA
/* inner and outer write back, read allocate */
#define NS_MAIR_NORMAL_CACHED_WB_RA 0xEE
/* uncached */
#define NS_MAIR_NORMAL_UNCACHED 0x44

/* Device memory */
/* nGnRnE (strongly ordered) */
#define NS_MAIR_DEVICE_STRONGLY_ORDERED 0x00
/* nGnRE  (device) */
#define NS_MAIR_DEVICE 0x04
/* GRE */
#define NS_MAIR_DEVICE_GRE 0x0C

/* shareable attributes bits 9:8 */
#define NS_PTE_SHAREABLE_SHIFT 8

#define NS_NON_SHAREABLE 0x0
#define NS_OUTER_SHAREABLE 0x2
#define NS_INNER_SHAREABLE 0x3

typedef uintptr_t addr_t;
typedef uintptr_t vaddr_t;
typedef uintptr_t paddr_t;

#if NS_ARCH_ARM64

#define PAR_F (0x1 << 0)

/*
 * ARM64
 */

/* Note: this will crash if called from user space */
static void arm64_write_ATS1ExW(uint64_t vaddr) {
    uint64_t _current_el;

    __asm__ volatile("mrs %0, CurrentEL" : "=r"(_current_el));

    _current_el = (_current_el >> 2) & 0x3;
    switch (_current_el) {
    case 0x1:
        __asm__ volatile("at S1E1W, %0" ::"r"(vaddr));
        break;
    case 0x2:
        __asm__ volatile("at S1E2W, %0" ::"r"(vaddr));
        break;
    case 0x3:
    default:
        trusty_fatal("Unsupported execution state: EL%u\n", _current_el);
        break;
    }

    __asm__ volatile("isb" ::: "memory");
}

static uint64_t arm64_read_par64(void) {
    uint64_t _val;
    __asm__ volatile("mrs %0, par_el1" : "=r"(_val));
    return _val;
}

static uint64_t va2par(vaddr_t va) {
    uint64_t par;
    unsigned long irq_state;

    trusty_local_irq_disable(&irq_state);
    arm64_write_ATS1ExW(va);
    par = arm64_read_par64();
    trusty_local_irq_restore(&irq_state);

    return par;
}

static uint64_t par2attr(uint64_t par) {
    uint64_t attr;

    /* set phys address */
    attr = NS_PTE_PHYSADDR(par);

    /* cache attributes */
    attr |= ((par >> 56) & 0xFF) << NS_PTE_MAIR_SHIFT;

    /* shareable attributes */
    attr |= ((par >> 7) & 0x03) << NS_PTE_SHAREABLE_SHIFT;

    /* the memory is writable and accessible so leave AP field 0 */
    attr |= 0x0 << NS_PTE_AP_SHIFT;

    return attr;
}

#else

#define PAR_F (0x1 << 0)
#define PAR_SS (0x1 << 1)
#define PAR_SH (0x1 << 7)
#define PAR_NOS (0x1 << 10)
#define PAR_LPAE (0x1 << 11)

/*
 * ARM32
 */

/* Note: this will crash if called from user space */
static void arm_write_ATS1xW(uint64_t vaddr) {
    uint32_t _cpsr;

    __asm__ volatile("mrs %0, cpsr" : "=r"(_cpsr));

    if ((_cpsr & 0xF) == 0xa)
        __asm__ volatile("mcr    p15, 4, %0, c7, c8, 1" : : "r"(vaddr));
    else
        __asm__ volatile("mcr    p15, 0, %0, c7, c8, 1" : : "r"(vaddr));
}

static uint64_t arm_read_par64(void) {
    uint32_t lower, higher;

    __asm__ volatile(
            "mrc    p15, 0, %0, c7, c4, 0   \n"
            "tst    %0, #(1 << 11)      @ LPAE / long desc format\n"
            "moveq  %1, #0          \n"
            "mrrcne p15, 0, %0, %1, c7  \n"
            : "=r"(lower), "=r"(higher)
            :
            :);

    return ((uint64_t)higher << 32) | lower;
}

static uint8_t ish_to_mair[8] = {
        0x04, /* 0b000 Non cacheble */
        0x00, /* 0b001 Strongly ordered */
        0xF0, /* 0b010 reserved */
        0x04, /* 0b011 device */
        0xF0, /* 0b100 reserved */
        0x0F, /* 0b101 write back - write allocate */
        0x0A, /* 0b110 write through */
        0x0E, /* 0b111 write back - no write allocate */
};

static uint8_t osh_to_mair[4] = {
        0x00, /* 0b00   Non-cacheable */
        0x0F, /* 0b01   Write-back, Write-allocate */
        0x0A, /* 0b10   Write-through, no Write-allocate */
        0x0E, /* 0b11   Write-back, no Write-allocate */
};

static uint64_t par2attr(uint64_t par) {
    uint64_t attr;

    if (par & PAR_LPAE) {
        /* set phys address */
        attr = NS_PTE_PHYSADDR(par);

        /* cache attributes */
        attr |= ((par >> 56) & 0xFF) << NS_PTE_MAIR_SHIFT;

        /* shareable attributes */
        attr |= ((par >> 7) & 0x03) << NS_PTE_SHAREABLE_SHIFT;

    } else {
        /* set phys address */
        trusty_assert((par & PAR_SS) == 0); /* super section not supported */
        attr = NS_PTE_PHYSADDR(par);

        /* cache attributes */
        uint64_t inner = ((uint64_t)ish_to_mair[(par >> 4) & 0x7])
                         << NS_PTE_MAIR_INNER_SHIFT;
        uint64_t outer = ((uint64_t)osh_to_mair[(par >> 2) & 0x3])
                         << NS_PTE_MAIR_OUTER_SHIFT;
        uint64_t cache_attributes = (outer << 4) | inner;

        /* Trusty does not support any kind of device memory, so we will force
         * cache attributes to be NORMAL UNCACHED on the Trusty side.
         */
        if (cache_attributes == NS_MAIR_DEVICE_STRONGLY_ORDERED) {
            attr |= ((uint64_t)NS_MAIR_NORMAL_UNCACHED << NS_PTE_MAIR_SHIFT);
        } else {
            attr |= inner;
            attr |= outer;
        }

        /* shareable attributes */
        if (par & PAR_SH) {
            /* how to handle NOS bit ? */
            attr |= ((uint64_t)NS_INNER_SHAREABLE) << NS_PTE_SHAREABLE_SHIFT;
        } else {
            attr |= ((uint64_t)NS_NON_SHAREABLE) << NS_PTE_SHAREABLE_SHIFT;
        }
    }

    /* the memory is writable and accessible so leave AP field 0 */
    attr |= 0x0 << NS_PTE_AP_SHIFT;

    return attr;
}

static uint64_t va2par(vaddr_t va) {
    uint64_t par;
    unsigned long irq_state;

    trusty_local_irq_disable(&irq_state);
    arm_write_ATS1xW(va);
    par = arm_read_par64();
    trusty_local_irq_restore(&irq_state);

    return par;
}

#endif /* ARM64 */

int trusty_encode_page_info(struct ns_mem_page_info* inf, void* va) {
    uint64_t par = va2par((vaddr_t)va);

    if (par & PAR_F) {
        return -1;
    }

    inf->attr = par2attr(par);

    return 0;
}
