/****************************************************************************
 * arch/xtensa/src/esp32/esp32_userspace.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <assert.h>
#include <debug.h>
#include <stdint.h>
#include <stdlib.h>

#include <nuttx/userspace.h>

#include <arch/board/board_memorymap.h>

#include "chip.h"
#include "xtensa.h"
#include "xtensa_attr.h"
#ifdef CONFIG_ESP32_USER_DATA_EXTMEM
#include "esp32_spiram.h"
#endif
#include "esp32_userspace.h"
#include "hardware/esp32_dport.h"
#include "hardware/esp32_pid.h"

#ifdef CONFIG_BUILD_PROTECTED

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define USER_IMAGE_OFFSET   CONFIG_ESP32_USER_IMAGE_OFFSET

#define MMU_BLOCK0_VADDR    SOC_DROM_LOW
#define MMU_SIZE            0x320000
#define MMU_BLOCK50_VADDR   (MMU_BLOCK0_VADDR + MMU_SIZE)

/* Cache MMU block size */

#define MMU_BLOCK_SIZE      0x00010000  /* 64 KB */

/* Cache MMU address mask (MMU tables ignore bits which are zero) */

#define MMU_FLASH_MASK      (~(MMU_BLOCK_SIZE - 1))

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct user_image_load_header_s
{
  uintptr_t drom_vma;      /* Destination address (VMA) for DROM region */
  uintptr_t drom_lma;      /* Flash offset (LMA) for start of DROM region */
  uintptr_t drom_size;     /* Size of DROM region */
  uintptr_t irom_vma;      /* Destination address (VMA) for IROM region */
  uintptr_t irom_lma;      /* Flash offset (LMA) for start of IROM region */
  uintptr_t irom_size;     /* Size of IROM region */
};

/****************************************************************************
 * ROM Function Prototypes
 ****************************************************************************/

extern void cache_read_enable(int cpu);
extern void cache_read_disable(int cpu);
extern void cache_flush(int cpu);
extern unsigned int cache_flash_mmu_set(int cpu_no, int pid,
                                        unsigned int vaddr,
                                        unsigned int paddr,
                                        int psize, int num);

/****************************************************************************
 * Private Data
 ****************************************************************************/

static struct user_image_load_header_s g_header;

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: calc_mmu_pages
 *
 * Description:
 *   Calculate the required number of MMU pages for mapping a given region
 *   from External Flash into Internal RAM.
 *
 * Input Parameters:
 *   size          - Length of the region to map
 *   vaddr         - Starting External Flash offset to map to Internal RAM
 *
 * Returned Value:
 *   None.
 *
 ****************************************************************************/

static inline uint32_t calc_mmu_pages(uint32_t size, uint32_t vaddr)
{
  return (size + (vaddr - (vaddr & MMU_FLASH_MASK)) + MMU_BLOCK_SIZE - 1) /
    MMU_BLOCK_SIZE;
}

/****************************************************************************
 * Name: configure_flash_mmu
 *
 * Description:
 *   Configure the External Flash MMU and Cache for enabling access to code
 *   and read-only data of the userspace image.
 *
 * Input Parameters:
 *   None.
 *
 * Returned Value:
 *   None.
 *
 ****************************************************************************/

static noinline_function IRAM_ATTR void configure_flash_mmu(void)
{
  uint32_t drom_lma_aligned;
  uint32_t drom_vma_aligned;
  uint32_t drom_page_count;
  uint32_t irom_lma_aligned;
  uint32_t irom_vma_aligned;
  uint32_t irom_page_count;

  size_t partition_offset = USER_IMAGE_OFFSET;
  uint32_t app_drom_lma = partition_offset + g_header.drom_lma;
  uint32_t app_drom_size = g_header.drom_size;
  uint32_t app_drom_vma = g_header.drom_vma;
  uint32_t app_irom_lma = partition_offset + g_header.irom_lma;
  uint32_t app_irom_size = g_header.irom_size;
  uint32_t app_irom_vma = g_header.irom_vma;

  cache_read_disable(0);
  cache_flush(0);

  drom_lma_aligned = app_drom_lma & MMU_FLASH_MASK;
  drom_vma_aligned = app_drom_vma & MMU_FLASH_MASK;
  drom_page_count = calc_mmu_pages(app_drom_size, app_drom_vma);
  ASSERT(cache_flash_mmu_set(0, PIDCTRL_PID_KERNEL, drom_vma_aligned,
                             drom_lma_aligned, 64,
                             (int)drom_page_count) == 0);
  ASSERT(cache_flash_mmu_set(1, PIDCTRL_PID_KERNEL, drom_vma_aligned,
                             drom_lma_aligned, 64,
                             (int)drom_page_count) == 0);
  ASSERT(cache_flash_mmu_set(0, PIDCTRL_PID_USER, drom_vma_aligned,
                             drom_lma_aligned, 64,
                             (int)drom_page_count) == 0);
  ASSERT(cache_flash_mmu_set(1, PIDCTRL_PID_USER, drom_vma_aligned,
                             drom_lma_aligned, 64,
                             (int)drom_page_count) == 0);

  irom_lma_aligned = app_irom_lma & MMU_FLASH_MASK;
  irom_vma_aligned = app_irom_vma & MMU_FLASH_MASK;
  irom_page_count = calc_mmu_pages(app_irom_size, app_irom_vma);
  ASSERT(cache_flash_mmu_set(0, PIDCTRL_PID_KERNEL, irom_vma_aligned,
                             irom_lma_aligned, 64,
                             (int)irom_page_count) == 0);
  ASSERT(cache_flash_mmu_set(1, PIDCTRL_PID_KERNEL, irom_vma_aligned,
                             irom_lma_aligned, 64,
                             (int)irom_page_count) == 0);
  ASSERT(cache_flash_mmu_set(0, PIDCTRL_PID_USER, irom_vma_aligned,
                             irom_lma_aligned, 64,
                             (int)irom_page_count) == 0);
  ASSERT(cache_flash_mmu_set(1, PIDCTRL_PID_USER, irom_vma_aligned,
                             irom_lma_aligned, 64,
                             (int)irom_page_count) == 0);

  cache_read_enable(0);
}

/****************************************************************************
 * Name: configure_sram_mmu
 *
 * Description:
 *   Configure the External SRAM MMU and Cache for enabling access to the
 *   data of the userspace image.
 *
 * Input Parameters:
 *   None.
 *
 * Returned Value:
 *   None.
 *
 ****************************************************************************/

#ifdef CONFIG_ESP32_USER_DATA_EXTMEM
static noinline_function IRAM_ATTR void configure_sram_mmu(void)
{
#ifdef CONFIG_SMP
  uint32_t regval;
#endif

  /* Enable external RAM in MMU */

  ASSERT(cache_sram_mmu_set(0, PIDCTRL_PID_KERNEL, SOC_EXTRAM_DATA_LOW, 0,
                            32, 128) == 0);
  ASSERT(cache_sram_mmu_set(0, PIDCTRL_PID_USER, SOC_EXTRAM_DATA_LOW, 0,
                            32, 128) == 0);

  /* Flush and enable icache for APP CPU */

#ifdef CONFIG_SMP
  regval  = getreg32(DPORT_APP_CACHE_CTRL1_REG);
  regval &= ~(1 << DPORT_APP_CACHE_MASK_DRAM1);
  putreg32(regval, DPORT_APP_CACHE_CTRL1_REG);
  ASSERT(cache_sram_mmu_set(1, PIDCTRL_PID_KERNEL, SOC_EXTRAM_DATA_LOW, 0,
                            32, 128) == 0);
  ASSERT(cache_sram_mmu_set(1, PIDCTRL_PID_USER, SOC_EXTRAM_DATA_LOW, 0,
                            32, 128) == 0);
#endif
}
#endif

/****************************************************************************
 * Name: map_flash
 *
 * Description:
 *   Map a region of the External Flash memory to Internal RAM.
 *
 * Input Parameters:
 *   src_addr      - Starting External Flash offset to map to Internal RAM
 *   size          - Length of the region to map
 *
 * Returned Value:
 *   None.
 *
 ****************************************************************************/

static noinline_function IRAM_ATTR const void *map_flash(uint32_t src_addr,
                                                         uint32_t size)
{
  uint32_t src_addr_aligned;
  uint32_t page_count;

  cache_read_disable(0);
  cache_flush(0);

  src_addr_aligned = src_addr & MMU_FLASH_MASK;
  page_count = calc_mmu_pages(size, src_addr);
  ASSERT(cache_flash_mmu_set(0, PIDCTRL_PID_KERNEL, MMU_BLOCK50_VADDR,
                             src_addr_aligned, 64, (int)page_count) == 0);

  cache_read_enable(0);

  return (void *)(MMU_BLOCK50_VADDR + (src_addr - src_addr_aligned));
}

/****************************************************************************
 * Name: load_header
 *
 * Description:
 *   Load IROM and DROM information from image header to enable the correct
 *   configuration of the Flash MMU and Cache.
 *
 * Input Parameters:
 *   None.
 *
 * Returned Value:
 *   None.
 *
 ****************************************************************************/

static void load_header(void)
{
  size_t length = sizeof(struct user_image_load_header_s);
  const uint8_t *data =
    (const uint8_t *)map_flash(USER_IMAGE_OFFSET, length);

  DEBUGASSERT(data != NULL);

  memcpy(&g_header, data, length);
}

/****************************************************************************
 * Name: initialize_data
 *
 * Description:
 *   Initialize data sections of the userspace image.
 *
 * Input Parameters:
 *   None.
 *
 * Returned Value:
 *   None.
 *
 ****************************************************************************/

static void initialize_data(void)
{
  uint8_t *dest;
  uint8_t *end;
  size_t length = USERSPACE->us_dataend - USERSPACE->us_datastart;
  const uint8_t *src =
    (const uint8_t *)map_flash(USER_IMAGE_OFFSET + USERSPACE->us_datasource,
                               length);

  DEBUGASSERT(src != NULL);

  dest = (uint8_t *)USERSPACE->us_datastart;
  end  = (uint8_t *)USERSPACE->us_dataend;

  while (dest != end)
    {
      *dest++ = *src++;
    }
}

/****************************************************************************
 * Name: configure_mpu
 *
 * Description:
 *   Configure the MPU for kernel/userspace separation.
 *
 * Input Parameters:
 *   None.
 *
 * Returned Value:
 *   None.
 *
 ****************************************************************************/

static void configure_mpu(void)
{
  /* 8K page size for mappings both IRAM and DRAM */

  REG_SET_FIELD(DPORT_IMMU_PAGE_MODE_REG, DPORT_IMMU_PAGE_MODE, 0);
  REG_SET_FIELD(DPORT_DMMU_PAGE_MODE_REG, DPORT_DMMU_PAGE_MODE, 0);

  /* 1:1 mapping for IRAM */

  REG_SET_FIELD(DPORT_IMMU_TABLE0_REG,  DPORT_IMMU_TABLE0,  0x00);
  REG_SET_FIELD(DPORT_IMMU_TABLE1_REG,  DPORT_IMMU_TABLE1,  0x51);
  REG_SET_FIELD(DPORT_IMMU_TABLE2_REG,  DPORT_IMMU_TABLE2,  0x52);
  REG_SET_FIELD(DPORT_IMMU_TABLE3_REG,  DPORT_IMMU_TABLE3,  0x53);
  REG_SET_FIELD(DPORT_IMMU_TABLE4_REG,  DPORT_IMMU_TABLE4,  0x54);
  REG_SET_FIELD(DPORT_IMMU_TABLE5_REG,  DPORT_IMMU_TABLE5,  0x55);
  REG_SET_FIELD(DPORT_IMMU_TABLE6_REG,  DPORT_IMMU_TABLE6,  0x56);
  REG_SET_FIELD(DPORT_IMMU_TABLE7_REG,  DPORT_IMMU_TABLE7,  0x57);
  REG_SET_FIELD(DPORT_IMMU_TABLE8_REG,  DPORT_IMMU_TABLE8,  0x08);
  REG_SET_FIELD(DPORT_IMMU_TABLE9_REG,  DPORT_IMMU_TABLE9,  0x09);
  REG_SET_FIELD(DPORT_IMMU_TABLE10_REG, DPORT_IMMU_TABLE10, 0x0a);
  REG_SET_FIELD(DPORT_IMMU_TABLE11_REG, DPORT_IMMU_TABLE11, 0x0b);
  REG_SET_FIELD(DPORT_IMMU_TABLE12_REG, DPORT_IMMU_TABLE12, 0x0c);
  REG_SET_FIELD(DPORT_IMMU_TABLE13_REG, DPORT_IMMU_TABLE13, 0x0d);
  REG_SET_FIELD(DPORT_IMMU_TABLE14_REG, DPORT_IMMU_TABLE14, 0x0e);
  REG_SET_FIELD(DPORT_IMMU_TABLE15_REG, DPORT_IMMU_TABLE15, 0x0f);

#ifdef CONFIG_ESP32_USER_DATA_EXTMEM
  REG_SET_FIELD(DPORT_DMMU_TABLE0_REG,  DPORT_DMMU_TABLE0,  0x00);
  REG_SET_FIELD(DPORT_DMMU_TABLE1_REG,  DPORT_DMMU_TABLE1,  0x01);
  REG_SET_FIELD(DPORT_DMMU_TABLE2_REG,  DPORT_DMMU_TABLE2,  0x02);
  REG_SET_FIELD(DPORT_DMMU_TABLE3_REG,  DPORT_DMMU_TABLE3,  0x03);
  REG_SET_FIELD(DPORT_DMMU_TABLE4_REG,  DPORT_DMMU_TABLE4,  0x04);
  REG_SET_FIELD(DPORT_DMMU_TABLE5_REG,  DPORT_DMMU_TABLE5,  0x05);
  REG_SET_FIELD(DPORT_DMMU_TABLE6_REG,  DPORT_DMMU_TABLE6,  0x06);
  REG_SET_FIELD(DPORT_DMMU_TABLE7_REG,  DPORT_DMMU_TABLE7,  0x07);
  REG_SET_FIELD(DPORT_DMMU_TABLE8_REG,  DPORT_DMMU_TABLE8,  0x08);
  REG_SET_FIELD(DPORT_DMMU_TABLE9_REG,  DPORT_DMMU_TABLE9,  0x09);
  REG_SET_FIELD(DPORT_DMMU_TABLE10_REG, DPORT_DMMU_TABLE10, 0x0a);
  REG_SET_FIELD(DPORT_DMMU_TABLE11_REG, DPORT_DMMU_TABLE11, 0x0b);
  REG_SET_FIELD(DPORT_DMMU_TABLE12_REG, DPORT_DMMU_TABLE12, 0x0c);
  REG_SET_FIELD(DPORT_DMMU_TABLE13_REG, DPORT_DMMU_TABLE13, 0x0d);
  REG_SET_FIELD(DPORT_DMMU_TABLE14_REG, DPORT_DMMU_TABLE14, 0x0e);
  REG_SET_FIELD(DPORT_DMMU_TABLE15_REG, DPORT_DMMU_TABLE15, 0x0f);
#else
  REG_SET_FIELD(DPORT_DMMU_TABLE0_REG,  DPORT_DMMU_TABLE0,  0x00);
  REG_SET_FIELD(DPORT_DMMU_TABLE1_REG,  DPORT_DMMU_TABLE1,  0x01);
  REG_SET_FIELD(DPORT_DMMU_TABLE2_REG,  DPORT_DMMU_TABLE2,  0x02);
  REG_SET_FIELD(DPORT_DMMU_TABLE3_REG,  DPORT_DMMU_TABLE3,  0x03);
  REG_SET_FIELD(DPORT_DMMU_TABLE4_REG,  DPORT_DMMU_TABLE4,  0x54);
  REG_SET_FIELD(DPORT_DMMU_TABLE5_REG,  DPORT_DMMU_TABLE5,  0x55);
  REG_SET_FIELD(DPORT_DMMU_TABLE6_REG,  DPORT_DMMU_TABLE6,  0x56);
  REG_SET_FIELD(DPORT_DMMU_TABLE7_REG,  DPORT_DMMU_TABLE7,  0x57);
  REG_SET_FIELD(DPORT_DMMU_TABLE8_REG,  DPORT_DMMU_TABLE8,  0x58);
  REG_SET_FIELD(DPORT_DMMU_TABLE9_REG,  DPORT_DMMU_TABLE9,  0x59);
  REG_SET_FIELD(DPORT_DMMU_TABLE10_REG, DPORT_DMMU_TABLE10, 0x5a);
  REG_SET_FIELD(DPORT_DMMU_TABLE11_REG, DPORT_DMMU_TABLE11, 0x5b);
  REG_SET_FIELD(DPORT_DMMU_TABLE12_REG, DPORT_DMMU_TABLE12, 0x5c);
  REG_SET_FIELD(DPORT_DMMU_TABLE13_REG, DPORT_DMMU_TABLE13, 0x5d);
  REG_SET_FIELD(DPORT_DMMU_TABLE14_REG, DPORT_DMMU_TABLE14, 0x5e);
  REG_SET_FIELD(DPORT_DMMU_TABLE15_REG, DPORT_DMMU_TABLE15, 0x5f);
#endif

  /* Configure interrupt vector addresses in PID Controller */

  putreg32(PIDCTRL_INTERRUPT_ENABLE_M, PIDCTRL_INTERRUPT_ENABLE_REG);

  /* Configure the PID Controller to switch to privileged mode (PID 0) when
   * the CPU fetches instruction from the following interrupt vectors:
   * 1) Level 1
   * 2) Window Overflow 4
   * 3) Level 3 (SWINT)
   * 4) Window Underflow 4
   * 5) Window Overflow 8
   * 6) Window Underflow 8
   * 7) NMI
   */

  putreg32(VECTORS_START + 0x340, PIDCTRL_INTERRUPT_ADDR_1_REG);
  putreg32(VECTORS_START + 0x000, PIDCTRL_INTERRUPT_ADDR_2_REG);
  putreg32(VECTORS_START + 0x1c0, PIDCTRL_INTERRUPT_ADDR_3_REG);
  putreg32(VECTORS_START + 0x040, PIDCTRL_INTERRUPT_ADDR_4_REG);
  putreg32(VECTORS_START + 0x080, PIDCTRL_INTERRUPT_ADDR_5_REG);
  putreg32(VECTORS_START + 0x0c0, PIDCTRL_INTERRUPT_ADDR_6_REG);
  putreg32(VECTORS_START + 0x2c0, PIDCTRL_INTERRUPT_ADDR_7_REG);

  putreg32(0, PIDCTRL_LEVEL_REG);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: esp32_userspace
 *
 * Description:
 *   For the case of the separate user/kernel space build, perform whatever
 *   platform specific initialization of the user memory is required.
 *   Normally this just means initializing the userspace .data and .bss
 *   segments.
 *
 * Input Parameters:
 *   None.
 *
 * Returned Value:
 *   None.
 *
 ****************************************************************************/

void esp32_userspace(void)
{
  uint8_t *dest;
  uint8_t *end;

  /* Load IROM and DROM information from image header */

  load_header();

  /* Configure the Flash MMU for enabling access to the userspace image */

  configure_flash_mmu();

#ifdef CONFIG_ESP32_USER_DATA_EXTMEM
  if (esp_spiram_init() != OK)
    {
      mwarn("SPI RAM initialization failed!\n");

      PANIC();
    }

  /* Configure the SRAM MMU for enabling access to the userspace image */

  configure_sram_mmu();
#endif

  /* Clear all of userspace .bss */

  DEBUGASSERT(USERSPACE->us_bssstart != 0 && USERSPACE->us_bssend != 0 &&
              USERSPACE->us_bssstart <= USERSPACE->us_bssend);

  dest = (uint8_t *)USERSPACE->us_bssstart;
  end  = (uint8_t *)USERSPACE->us_bssend;

  while (dest != end)
    {
      *dest++ = 0;
    }

  /* Initialize all of userspace .data */

  DEBUGASSERT(USERSPACE->us_datasource != 0 &&
              USERSPACE->us_datastart != 0 && USERSPACE->us_dataend != 0 &&
              USERSPACE->us_datastart <= USERSPACE->us_dataend);

  initialize_data();

  /* Configure MPU to grant access to the userspace */

  configure_mpu();
}

#endif /* CONFIG_BUILD_PROTECTED */
