/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdbool.h>
#include <stdint.h>

#include <bl.h>
#include <cpu.h>
#include <mpu.h>
#include <platform.h>

#include <plat/cmsis.h>

#define MPU_REG_DEFAULT     0
#define MPU_REG_ROM         1
#define MPU_REG_RAM         2
#define MPU_REG_PERIPH      3
#define MPU_REG_PRIV_PERIPH 4

#define MPU_RASR_S          0x00040000
#define MPU_RASR_C          0x00020000
#define MPU_RASR_B          0x00010000

/* region type */
#define MPU_TYPE_SRAM       (MPU_RASR_S | MPU_RASR_C)
#define MPU_TYPE_FLASH      (MPU_RASR_C)
#define MPU_TYPE_PERIPH     (MPU_RASR_S | MPU_RASR_B)

/* region execute priviledges */
#define MPU_BIT_XN          (1UL << 28) /* no execute */

/* region access priviledges */
#define MPU_NA              (0UL << 24) /* S: no access   U: no access */
#define MPU_U_NA_S_RW       (1UL << 24) /* S: RW          U: no access */
#define MPU_U_RO_S_RW       (2UL << 24) /* S: RW          U: RO        */
#define MPU_RW              (3UL << 24) /* S: RW          U: RW        */
#define MPU_U_NA_S_RO       (5UL << 24) /* S: RO          U: no access */
#define MPU_U_RO_S_RO       (6UL << 24) /* S: RO          U: RO        */

/* subregion disable (not used so all zeroes) */
#define MPU_SRD_BITS        0x0000UL
#define MPU_BIT_ENABLE      1UL

/* these define rom */
extern uint8_t __shared_end[];
extern uint8_t __ram_start[];
extern uint8_t __ram_end[];

void MemoryManagemntFault_Handler(void);
void __attribute__((naked)) MemoryManagemntFault_Handler(void)
{
    asm volatile(
        "mov    r0, #3                    \n"
        "b      cpuCommonFaultCode        \n"
    );
}

static bool mpuRegionCfg(uint32_t regionNo, uint32_t start, uint32_t end, uint32_t attrs) /* region will be rounded to acceptable boundaries (32B minimum, self-aligned) by GROWTH */
{
    uint32_t proposedStart, lenVal = 1;
    uint64_t len, proposedLen, intState;

    if (start > end)
        return false;
    else
        len = end - start + UINT64_C(1);

    /* expand until it works */
    do {
        proposedStart = start &~ ((UINT64_C(1) << lenVal) - 1);
        proposedLen = start + len - proposedStart;
        if (proposedLen < 32)
            proposedLen = 32;
        lenVal = (proposedLen & (proposedLen - UINT64_C(1))) ? 64 - __builtin_clzll(proposedLen) : 63 - __builtin_clzll(proposedLen);

    } while (proposedStart & ((UINT64_C(1) << lenVal) - UINT64_C(1)));

    /* minimum size: 32 bytes */
    if (lenVal < 5)
        lenVal = 5;

    intState = cpuIntsOff();
    asm volatile("dsb\nisb");

    MPU->RNR = regionNo;
    MPU->RASR = 0; /* disable region before changing it */
    MPU->RBAR = proposedStart;
    MPU->RASR = MPU_SRD_BITS | MPU_BIT_ENABLE | attrs | ((lenVal-1) << 1);

    asm volatile("dsb\nisb");
    cpuIntsRestore(intState);

    return true;
}

static void mpuCfgRom(bool allowSvcWrite)
{
    mpuRegionCfg(MPU_REG_ROM, (uint32_t)&BL, (uint32_t)&__shared_end - 1, MPU_TYPE_FLASH | (allowSvcWrite ? MPU_U_RO_S_RW : MPU_U_RO_S_RO));
}

static void mpuCfgRam(bool allowSvcExecute)
{
    mpuRegionCfg(MPU_REG_RAM, (uint32_t)&__ram_start, (uint32_t)&__ram_end - 1, MPU_TYPE_SRAM | MPU_RW | (allowSvcExecute ? 0 : MPU_BIT_XN));
}


void mpuStart(void)
{
    MPU->CTRL = 0x00; // disable MPU

    /* 0x00000000 - 0xFFFFFFFF */
    mpuRegionCfg(MPU_REG_DEFAULT, 0, 0xFFFFFFFF, MPU_NA | MPU_BIT_XN);

    mpuCfgRom(false);
    mpuCfgRam(false);

    /* 0x40000000 - 0x4003FFFF */
    mpuRegionCfg(MPU_REG_PERIPH, 0x40000000, 0x4003FFFF, MPU_TYPE_PERIPH | MPU_U_NA_S_RW | MPU_BIT_XN);

    /* 0xE0000000 - 0xE00FFFFF */
    mpuRegionCfg(MPU_REG_PRIV_PERIPH, 0xE0000000, 0xE00FFFFF, MPU_TYPE_PERIPH | MPU_U_NA_S_RW | MPU_BIT_XN);

    //MPU on, even during faults, supervisor default: allow, user default: default deny
    MPU->CTRL = MPU_CTRL_ENABLE_Msk | MPU_CTRL_HFNMIENA_Msk | MPU_CTRL_PRIVDEFENA_Msk;
    SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;
}

void mpuAllowRamExecution(bool allowSvcExecute)
{
    mpuCfgRam(allowSvcExecute);
}

void mpuAllowRomWrite(bool allowSvcWrite)
{
    mpuCfgRom(allowSvcWrite);
}

void mpuShow()
{
    int i, regions = (MPU->TYPE & MPU_TYPE_DREGION_Msk) >> MPU_TYPE_DREGION_Pos;
    uint32_t addr, rasr;
    uint8_t ap;
    bool xn;
    char *s, *u;

    osLog(LOG_INFO, "MPU: %d HFNMIENA: %d PRIVDEFENA: %d\n",
        !!(MPU->CTRL & MPU_CTRL_ENABLE_Msk),
        !!(MPU->CTRL & MPU_CTRL_HFNMIENA_Msk),
        !!(MPU->CTRL & MPU_CTRL_PRIVDEFENA_Msk));
    for (i=0; i<regions; i++) {
        MPU->RNR = i;
        addr = MPU->RBAR & MPU_RBAR_ADDR_Msk;
        rasr = MPU->RASR;
        xn = rasr & MPU_RASR_XN_Msk;
        ap = (rasr & MPU_RASR_AP_Msk) >> MPU_RASR_AP_Pos;
        if (ap == 0) {
            s = "---";
        } else if (ap == 1 || ap == 2 || ap == 3) {
            if (xn)
                s = "RW-";
            else
                s = "RWX";
        } else if (ap == 5 || ap == 6 || ap == 7) {
            if (xn)
                s = "R--";
            else
                s = "R-X";
        } else {
            s = "???";
        }
        if (ap == 0 || ap == 1 || ap == 5) {
            u = "---";
        } else if (ap == 3) {
            if (xn)
                u = "RW-";
            else
                u = "RWX";
        } else if (ap == 2 || ap == 6 || ap == 7) {
            if (xn)
                u = "R--";
            else
                u = "R-X";
        } else {
            u = "???";
        }
        osLog(LOG_INFO,
            "%d: %c %08lx-%08lx S: %s U: %s TEX: %ld %c%c%c %02lx\n",
            i, (rasr & MPU_RASR_ENABLE_Msk) ? 'E' : 'D',
            addr,
            addr + (1 << (((rasr & MPU_RASR_SIZE_Msk) >> MPU_RASR_SIZE_Pos) + 1))-1,
            s, u,
            (rasr & MPU_RASR_TEX_Msk) >> MPU_RASR_TEX_Pos,
            (rasr & MPU_RASR_S_Msk) ? 'S' : ' ',
            (rasr & MPU_RASR_C_Msk) ? 'C' : ' ',
            (rasr & MPU_RASR_B_Msk) ? 'B' : ' ',
            (rasr & MPU_RASR_SRD_Msk) >> MPU_RASR_SRD_Pos);
    }
}