/***************************************************************************
 *
 * Copyright 2015-2019 BES.
 * All rights reserved. All unpublished rights reserved.
 *
 * No part of this work may be used or reproduced in any form or by any
 * means, or stored in a database or retrieval system, without prior written
 * permission of BES.
 *
 * Use of this work is governed by a license granted by BES.
 * This work contains confidential and proprietary information of
 * BES. which is protected by copyright, trade secret,
 * trademark and other intellectual property rights.
 *
 ****************************************************************************/
#ifndef __ARM_ARCH_ISA_ARM

#include "mpu.h"
#include "cmsis.h"
#include "hal_trace.h"

int mpu_open(void)
{
    int i;

    if ((MPU->TYPE & MPU_TYPE_DREGION_Msk) == 0) {
        return 1;
    }

    ARM_MPU_Disable();

    for (i = 0; i < MPU_ID_QTY; i++) {
        ARM_MPU_ClrRegion(i);
    }

    ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk);

    return 0;
}

int mpu_close(void)
{
    ARM_MPU_Disable();

    return 0;
}

static int mpu_set_armv7(enum MPU_ID_T id, uint32_t addr, uint32_t len,
                                        int srd_bits, enum MPU_ATTR_T attr)
{
    int ret;
    uint32_t rbar;
    uint32_t rasr;
    uint8_t xn;
    uint8_t ap;
    uint8_t size;

    if ((MPU->CTRL & MPU_CTRL_ENABLE_Msk) == 0) {
        ret = mpu_open();
        if (ret) {
            return ret;
        }
    }

    if (id >= MPU_ID_QTY) {
        return 2;
    }
    if (len < 32 || (len & (len - 1))) {
        return 3;
    }
    if (addr & (len - 1)) {
        return 4;
    }
    if (attr >= MPU_ATTR_QTY) {
        return 5;
    }

    if (attr == MPU_ATTR_READ_WRITE_EXEC || attr == MPU_ATTR_READ_EXEC ||
            attr == MPU_ATTR_EXEC) {
        xn = 0;
    } else {
        xn = 1;
    }

    ap = ARM_MPU_AP_NONE;
    if (attr == MPU_ATTR_READ_WRITE_EXEC || attr == MPU_ATTR_READ_WRITE) {
        ap = ARM_MPU_AP_FULL;
    } else if (attr == MPU_ATTR_READ_EXEC || attr == MPU_ATTR_READ) {
        ap = ARM_MPU_AP_RO;
    }

    size = __CLZ(__RBIT(len)) - 1;

    ARM_MPU_Disable();
    rbar = ARM_MPU_RBAR(id, addr);
    // Type Extention: 0
    // Shareable: 1
    // Cacheable: 1
    // Bufferable: 1
    // Subregion: 0
    rasr = ARM_MPU_RASR(xn, ap, 0, 1, 1, 1, srd_bits, size);

    ARM_MPU_SetRegion(rbar, rasr);
    ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk);
    __DSB();
    __ISB();

    return 0;
}

/*
 * sub region is 8 bits, and if one bits is 1, the sub resion will be
 * disabled
 * like 0b00011111, the top 3 sub region will be disabled
 * if it is 0b11110000, the bottom 4 sub region will be disabled.
 */
static int mpu_get_top_srd(uint32_t rg_end, uint32_t fr_end,
                                                    uint32_t sub_rg_sz)
{
    int dis_nums;
    uint8_t srd_bits;

    dis_nums = (rg_end - fr_end) / sub_rg_sz;
    if ((fr_end & (sub_rg_sz - 1)) != 0)
        dis_nums += 1;

    srd_bits = 0xff;
    srd_bits &= ~((1UL << (8 - dis_nums)) - 1);

    return srd_bits;
}

static int mpu_get_bottom_srd(uint32_t rg_start, uint32_t fr_start,
                                                    uint32_t sub_rg_sz)
{
    int dis_nums;
    uint8_t srd_bits;

    dis_nums = (fr_start - rg_start) / sub_rg_sz;
    srd_bits = 0xff;
    srd_bits &= ((1UL << dis_nums) - 1);

    return srd_bits;
}

static int calc_sub_region_size(uint32_t fr_start)
{
    int lsb_bit;
    uint32_t sz;

    //sub region size aligned to fram start
    lsb_bit = get_lsb_pos(fr_start);
    sz = 1 << lsb_bit;
    if (sz < 0x80) {
        /*cortex-m4 doesn't support sub region size less than 128 */
        TRACE(1,"no mpu region for fram start %x", fr_start);
        return -1;
    }
    if (sz > 0x4000)
        sz = 0x4000;


    return sz;
}

/*
Allocate two mpu sections to protect the fast ram.  The cortext M4
mpu has a lot of restrict, such as one region's start address must
be aligned to the size of region, and the sub region number is fixed
to 8.
The layout like:
          ------------------
          | sub region 8   |
          ------------------
          | sub region 7   |
          | .....          |
          |                |
          ------------------>fast_ram_end
          |////////////////|
          |////////////////|
          |////////////////|
          |////////////////|
          ------------------>mpu region2
          |//sub region 8//|
          |////////////////|
          |////////////////|
          |////////////////|
          ------------------>fast_ram_start aligned to sub region size
          | .....          |
          |                |
          |sub region 1    |
          ------------------
          |sub region 0    |
          ------------------>mpu region1

If the __fast_sram_text_exec_end__ exceed the region2's end, just leave
the rest unprotect.
*/
static int mpu_fram_protect_armv7(uint32_t fr_start, uint32_t fr_end)
{
    uint32_t fr_sz;
    uint32_t sub_rg_sz;
    uint32_t rg_sz;
    uint32_t rg_start;
    uint32_t rg_end;
    int ret = 0;
    int srd_bits;
    int finished = 0;

    fr_sz = fr_end - fr_start;
    sub_rg_sz = calc_sub_region_size(fr_start);
    if (sub_rg_sz < 0)
        return -1;

    /* according to cortex-m4 spec, the region is divived to 8 sub region*/
    rg_sz = sub_rg_sz * 8;
    /*now we just protect two region */
    if (fr_sz > (rg_sz * 2)) {
        TRACE(0,"Warning fram is too big, mpu can not protect so much");
        TRACE(2,"region_sz %x, fram size %x", rg_sz, fr_sz);
    }

    /*aliged the region start to region size according to cortext m4 spec*/
    srd_bits = 0;
    rg_start = fr_start & ~(rg_sz - 1);
    if (fr_start > rg_start) {
        int b_srd_bits = 0;

        b_srd_bits = mpu_get_bottom_srd(rg_start, fr_start, sub_rg_sz);
        srd_bits |= b_srd_bits;
    }

    rg_end = rg_start + rg_sz;
    if (fr_end < rg_end) {
        int t_srd_bits = 0;

        t_srd_bits = mpu_get_top_srd(rg_end, fr_end, sub_rg_sz);
        srd_bits |= t_srd_bits;
        finished = 1;
    }

    ret = mpu_set(MPU_ID_FRAM_TEXT1, rg_start, rg_sz, srd_bits, MPU_ATTR_READ);
    if (ret || finished)
        goto out;

    /* need another section, and start from next region*/
    rg_start += rg_sz;

    srd_bits = 0;
    rg_end = rg_start + rg_sz;
    if (fr_end < rg_end) {
        srd_bits = mpu_get_top_srd(rg_end, fr_end, sub_rg_sz);
    }

    ret = mpu_set(MPU_ID_FRAM_TEXT2, rg_start, rg_sz, srd_bits, MPU_ATTR_READ_EXEC);
    /* if fr_end large than two section, just pass */
out:
    return ret;
}

int mpu_set(enum MPU_ID_T id, uint32_t addr, uint32_t len, int srd_bits,
                                                        enum MPU_ATTR_T attr)
{
    return mpu_set_armv7(id, addr, len, srd_bits, attr);
}

int mpu_clear(enum MPU_ID_T id)
{
    uint32_t lock;

    if (id >= MPU_ID_QTY) {
        return 2;
    }

    lock = int_lock();

    ARM_MPU_ClrRegion(id);
    __DSB();
    __ISB();

    int_unlock(lock);

    return 0;
}

static int mpu_null_check_enable(void)
{
#ifdef CHIP_BEST1000
    uint32_t len = 0x400;
#else
    uint32_t len = 0x800;
#endif

    return mpu_set(MPU_ID_NULL_POINTER, 0, len, 0, MPU_ATTR_NO_ACCESS);
}

extern uint32_t __fast_sram_text_exec_start__[];
extern uint32_t __fast_sram_text_exec_end__[];

static int mpu_fast_ram_protect(void)
{
    uint32_t ramx_start  = (uint32_t)__fast_sram_text_exec_start__;
    uint32_t ramx_end = (uint32_t)__fast_sram_text_exec_end__;
    uint32_t ram_start = RAMX_TO_RAM(ramx_start);
    uint32_t ram_end = RAMX_TO_RAM(ramx_end);

    return mpu_fram_protect_armv7(ram_start, ram_end);
}

#if 0
int mpu_fram_test(void)
{
    uint32_t ramx_start  = (uint32_t)__fast_sram_text_exec_start__;
    uint32_t ram_start = RAMX_TO_RAM(ramx_start);
    uint32_t test_start =  ram_start - 1024;

    for (int i = 0; i < 1024; i++) {
        TRACE(1,"test_start %x", test_start);
        *(volatile uint32_t *)test_start = 0x1;
        test_start += 128;
    }

    uint32_t ramx_end = (uint32_t)__fast_sram_text_exec_end__;
    uint32_t ram_end = RAMX_TO_RAM(ramx_end);
    uint32_t test_end =  ram_end + 1024;
    for (int i = 0; i < 1024; i++) {
        TRACE(1,"test_end %x", test_end);
        *(volatile uint32_t *)test_end = 0x1;
        test_end -= 128;
    }

    return 0;
}
#endif

int mpu_setup(void)
{
    mpu_null_check_enable();
    mpu_fast_ram_protect();

    return 0;
}

int mpu_setup_cp(const mpu_regions_t *mpu_table, uint32_t region_num)
{
    int ret;
    int i;

    ret = mpu_open();
    if (ret) {
        return ret;
    }

    if (region_num > MPU_ID_QTY) {
        return -1;
    }

    for (i = 0; i < region_num; i++) {
        const mpu_regions_t *region;
        region = &mpu_table[i];
        ret = mpu_set(i, region->addr, region->len, 0, region->ap_attr);
        if (ret)
            break;
    }

    return ret;
}

#endif
