/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * The code in this file is copied (with a slight modifications and additions) from:
 *     mmu_iram.h
 * ----------------------------------------------------------------------------------
 * Copyright 2020 M Hightower
 *
 * 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.
 * ----------------------------------------------------------------------------------
 */


#ifndef ECXX_PLATFORM_NODEMCU_SYS_MMU_IRAM_H
#define ECXX_PLATFORM_NODEMCU_SYS_MMU_IRAM_H


#include <eCxx/CTL/Types.h>


#ifdef __cplusplus
extern "C" {
#endif


// This turns on range checking
#ifdef DEBUG_ESP_CORE
    #define DEBUG_ESP_MMU
#endif


#ifdef CORE_MOCK
    #define ets_uart_printf(...) do {} while(0)
#endif


#if defined(DEV_DEBUG_PRINT) || defined(DEBUG_ESP_MMU)

    #include <esp8266_peri.h>

    #define DBG_MMU_FLUSH(A) while( (USS(A) >> USTXC) & 0xFF ) {}

    #ifdef DEV_DEBUG_PRINT

        extern void set_pll();
        extern void dbg_set_pll();

        #define DBG_MMU_PRINTF(FMT, ...)         \
            set_pll();                           \
            uart_buff_switch(0);                 \
            ets_uart_printf(FMT, ##__VA_ARGS__); \
            DBG_MMU_FLUSH(0)

    #else

        #define DBG_MMU_PRINTF(FMT, ...) ets_uart_printf(FMT, ##__VA_ARGS__)

    #endif // DEV_DEBUG_PRINT

#else

    #define DBG_MMU_FLUSH(...)  do {} while(0)
    #define DBG_MMU_PRINTF(...) do {} while(0)

#endif // defined(DEV_DEBUG_PRINT) || defined(DEBUG_ESP_MMU)


#ifdef DEBUG_ESP_MMU

    #define ASSERT_RANGE_TEST_WRITE(A)                       \
        if( mmu_is_iram(A) || mmu_is_dram(A) ) {             \
        }                                                    \
        else {                                               \
            DBG_MMU_PRINTF("\nexcvaddr: %p\n", A);           \
            assert( ("Outside of range - write" && false) ); \
        }

    #define ASSERT_RANGE_TEST_READ(A)                                \
        if( mmu_is_iram(A) || mmu_is_dram(A) || mmu_is_icache(A) ) { \
        }                                                            \
        else {                                                       \
            DBG_MMU_PRINTF("\nexcvaddr: %p\n", A);                   \
            assert( ("Outside of range - read" && false) );          \
        }

#else

    #define ASSERT_RANGE_TEST_WRITE(A) do {} while(0)
    #define ASSERT_RANGE_TEST_READ(A)  do {} while(0)

#endif // DEBUG_ESP_MMU


static inline __ECXX_ATTR_ALWAYS_INLINE bool mmu_is_iram(const void* addr)
{
    #define IRAM_START 0x40100000UL

    #ifndef MMU_IRAM_SIZE
        #if defined(__GNUC__) && !defined(CORE_MOCK)
            #warning "MMU_IRAM_SIZE was undefined, setting it to 0x8000UL!"
        #endif
        #define MMU_IRAM_SIZE 0x8000UL
    #endif

    #define IRAM_END (IRAM_START + MMU_IRAM_SIZE)

    return ( IRAM_START <= reinterpret_cast<uintptr_t>(addr) && IRAM_END > reinterpret_cast<uintptr_t>(addr) );
}


static inline __ECXX_ATTR_ALWAYS_INLINE bool mmu_is_dram(const void* addr)
{
    #define DRAM_START 0x3FF80000UL
    #define DRAM_END   0x40000000UL

    return ( DRAM_START <= reinterpret_cast<uintptr_t>(addr) && DRAM_END > reinterpret_cast<uintptr_t>(addr) );
}


static inline __ECXX_ATTR_ALWAYS_INLINE bool mmu_is_icache(const void* addr)
{
    #define ICACHE_START 0x40200000UL
    #define ICACHE_END   (ICACHE_START + 0x100000UL)

    return ( ICACHE_START <= reinterpret_cast<uintptr_t>(addr) && ICACHE_END > reinterpret_cast<uintptr_t>(addr) );
}


static inline __ECXX_ATTR_ALWAYS_INLINE uint8_t mmu_get_uint8(const void* p8)
{
    ASSERT_RANGE_TEST_READ(p8);

    const uint32_t val = *reinterpret_cast<uint32_t*>( reinterpret_cast<uintptr_t>(p8) & ~0x03U );
    const uint32_t pos = ( reinterpret_cast<uintptr_t>(p8) & 0x03U ) * 8;

    return static_cast<uint8_t>(val >> pos);
}


static inline __ECXX_ATTR_ALWAYS_INLINE uint16_t mmu_get_uint16(const uint16_t* p16)
{
    ASSERT_RANGE_TEST_READ(p16);

    const uint32_t val = *reinterpret_cast<uint32_t*>( reinterpret_cast<uintptr_t>(p16) & ~0x03U );
    const uint32_t pos = ( reinterpret_cast<uintptr_t>(p16) & 0x03U ) * 8;

    return static_cast<uint16_t>(val >> pos);
}


static inline __ECXX_ATTR_ALWAYS_INLINE int16_t mmu_get_int16(const int16_t* p16)
{
    ASSERT_RANGE_TEST_READ(p16);

    const uint32_t val = *reinterpret_cast<uint32_t*>( reinterpret_cast<uintptr_t>(p16) & ~0x03U );
    const uint32_t pos = ( reinterpret_cast<uintptr_t>(p16) & 0x03U ) * 8;

    return static_cast<int16_t>(val >> pos);
}


static inline __ECXX_ATTR_ALWAYS_INLINE uint8_t mmu_set_uint8(void* p8, const uint8_t val)
{
    ASSERT_RANGE_TEST_WRITE(p8);

    const uint32_t pos     = ( reinterpret_cast<uintptr_t>(p8) & 0x03U ) * 8;
    const uint32_t sval    = val << pos;
    const uint32_t valmask = 0xFFU << pos;

    uint32_t* p32  = reinterpret_cast<uint32_t*>( reinterpret_cast<uintptr_t>(p8) & ~0x03U );
    uint32_t  ival = *p32;

    ival &= (~valmask);
    ival |= sval;

    *p32 = ival;

    return val;
}


static inline __ECXX_ATTR_ALWAYS_INLINE uint16_t mmu_set_uint16(uint16_t* p16, const uint16_t val)
{
    ASSERT_RANGE_TEST_WRITE(p16);

    const uint32_t pos     = ( reinterpret_cast<uintptr_t>(p16) & 0x03U ) * 8;
    const uint32_t sval    = val << pos;
    const uint32_t valmask = 0xFFFFU << pos;

    uint32_t* p32  = reinterpret_cast<uint32_t*>( reinterpret_cast<uintptr_t>(p16) & ~0x03U );
    uint32_t  ival = *p32;

    ival &= (~valmask);
    ival |= sval;

    *p32 = ival;

    return val;
}


static inline __ECXX_ATTR_ALWAYS_INLINE int16_t mmu_set_int16(int16_t* p16, const int16_t val)
{
    ASSERT_RANGE_TEST_WRITE(p16);

    const uint32_t sval    = static_cast<uint16_t>(val);
    const uint32_t pos     = ( reinterpret_cast<uintptr_t>(p16) & 0x03U ) * 8;
    const uint32_t valmask = 0xFFFFU << pos;

    uint32_t* p32  = reinterpret_cast<uint32_t*>( reinterpret_cast<uintptr_t>(p16) & ~0x03U );
    uint32_t  ival = *p32;

    ival &= (~valmask);
    ival |= (sval << pos);

    *p32 = ival;

    return val;
}


#if (MMU_IRAM_SIZE > 32 * 1024) && !defined(MMU_SEC_HEAP)

    extern void _text_end();

    #define MMU_SEC_HEAP      mmu_sec_heap()
    #define MMU_SEC_HEAP_SIZE mmu_sec_heap_size()

    static inline __ECXX_ATTR_ALWAYS_INLINE void* mmu_sec_heap()
    {
        const uint32_t sec_heap = reinterpret_cast<uint32_t>(_text_end) + 32;

        return reinterpret_cast<void*>(sec_heap & ~0x07U);
    }

    static inline __ECXX_ATTR_ALWAYS_INLINE size_t mmu_sec_heap_size()
    {
        return static_cast<size_t>(0xC000UL) - ( reinterpret_cast<size_t>( mmu_sec_heap() ) - 0x40100000UL );
    }

#endif


#ifdef __cplusplus
} // extern "C"
#endif


#endif
