/*
* Copyright (c) 2020 AIIT XUOS Lab
* XiUOS is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*        http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/

#include "stm32f4xx.h"
#include "mpu.h"
#include "board.h"
#include <xs_isolation.h>


void MpuEnable(uint32_t option)
{
    MPU->CTRL = MPU_ENABLE | option;
    __DSB();
    __ISB();
    return ;
}

void MpuDisable(void)
{
    __DMB();
    MPU->CTRL = 0;
    return;
}

void MpuClearRegion(uint8_t region)
{
    __DMB();
    MPU->RNR  = region;
    MPU->RBAR = 0;
    MPU->RASR = 0;
    return;
}

void MpuClearAllRegion(void)
{
    __DMB();
    for (int i = 0 ; i < 8 ; i++) {
        MPU->RNR  = i;
        MPU->RBAR = 0;
        MPU->RASR = 0;
    }
    return;
}

int8_t MpuAllocateRegion(void *task_mpu)
{
    if(task_mpu == NONE)
       return -1;
    struct Mpu *mpu = (struct Mpu *)task_mpu;
    int8_t free_region;

    if ( mpu->count < MPU_MAX_REGION_NUM - 1) {
        free_region = mpu->count;
        mpu->count ++;
    }
    else
      free_region = -1;

    return free_region ;
}


size_t MpuLog2Ceil(size_t size)
{
    size_t csize = 0;

    while (size > 0) {
        size >>= 1;
        csize ++;
    }
    
    return csize;
}

int8_t MpuAddRegion(void *task_mpu, x_base addr , size_t size , uint8_t type)
{

    if(task_mpu == NONE)
       return -1;
    struct Mpu *mpu = (struct Mpu *)task_mpu;
    uint32_t l2size;
    int8_t region ;
    
    region = MpuAllocateRegion(mpu);
    if (region < 0 ) {
        // KPrintf("MPU region full \n");
        return region;
    }
    uint32_t flag = 0;
    switch (type)
    {
    case REGION_TYPE_CODE:
        flag = MPU_RASR_AP_RO_RO  | MPU_RASR_TEX_0 | MPU_RASR_C  | MPU_RASR_S;
        break;
    case REGION_TYPE_DATA:
        flag = MPU_RASR_AP_RW_RW | MPU_RASR_TEX_0 | MPU_RASR_C  | MPU_RASR_S ;
        break;
    case REGION_TYPE_BSS:
        flag = MPU_RASR_AP_RW_RW | MPU_RASR_TEX_0 | MPU_RASR_C  | MPU_RASR_S ;
        break;
    case REGION_TYPE_HEAP:
        flag = MPU_RASR_AP_RW_RW | MPU_RASR_TEX_0 | MPU_RASR_C  | MPU_RASR_S ;
        break;
    
    default:
        break;
    }

    l2size  = MpuLog2Ceil(size);
    addr = MPU_ALIGN(addr , 1 << l2size );
    mpu->region[region].config.rasr =  flag |MPU_RASR_REGION_SIZE(l2size)  | MPU_ENABLE;
    mpu->region[region].config.rbar =  addr | MPU_RBAR_VALID | region ;     //rbar must set region number

    return region;
}

x_err_t MpuInit(void **task_mpu)
{
    
    x_base   addr_start;
    uint32_t addr_size;
    uint32_t flag;

    if (MPU->TYPE == 0) {
        KPrintf("mpu not surport \n");
        return -1 ;
    }
    // KPrintf("mpu init ...\n");

    struct Mpu *mpu;
    mpu = (struct Mpu *)malloc(sizeof(struct Mpu));
    memset(mpu, 0, sizeof(struct Mpu));

    if (mpu == NONE)
       return -ENOMEMORY;

    MpuDisable();
    MpuClearAllRegion(); //clear 

    SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;

    //user flash
    addr_start = USER_TEXT_START;
    addr_size  = USER_TEXT_END - USER_TEXT_START;
    MpuAddRegion(mpu, addr_start , addr_size, REGION_TYPE_CODE);


    //user sram 
    addr_start = USER_SRAM_START;
    addr_size  = USER_SRAM_END - USER_SRAM_START;
    MpuAddRegion(mpu, addr_start , addr_size, REGION_TYPE_HEAP);
    *task_mpu = mpu;

    return EOK;
}

void MpuFree(void *task_mpu)
{
    if(task_mpu == NONE)
       return;
    MpuDisable();
    MpuClearAllRegion(); //clear 
    x_free(task_mpu);
}

void MpuLoad(void *task_mpu)
{
    // KPrintf("MPU load .. \n");
    MpuDisable();
    MpuClearAllRegion(); //clear 
    if(task_mpu == NONE)
       return;
    struct Mpu *mpu = (struct Mpu *)task_mpu;

    uint8_t region = 0 ;

#if 0
    for ( region = 0 ; region <= mpu->count -1  ; region ++ ){
        KPrintf("region: %d\n",region);
        KPrintf("rasr 0x%08x \n",mpu->region[region].config.rasr);
        KPrintf("rbar 0x%08x \n",mpu->region[region].config.rbar);
        KPrintf("\n");
    }
#endif

   for ( region = 0 ; region <= mpu->count -1 ; region ++ ){
       MPU->RNR  = region;
       MPU->RBAR = mpu->region[region].config.rbar;
       MPU->RASR = mpu->region[region].config.rasr;
   }
   __DSB();
   __ISB();
   MpuEnable(MPU_PRIVDEFENA);
}