/*
 * 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) from:
 *     StackThunk.cpp - allow use second stack for subsystem that needs a significant
 *                      amount of stack space
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2017 Earle F. Philhower, III. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 * ----------------------------------------------------------------------------------
 */


#include <umm_malloc/umm_heap_select.h>

#include "CoreSys.h"
#include "StackThunk.h"
#include "SysMMUIRAM.h"


extern "C" {


//
// Exported variables
//
eCxx::uint32_t* stack_thunk_ptr    = nullptr;
eCxx::uint32_t* stack_thunk_top    = nullptr;
eCxx::uint32_t* stack_thunk_save   = nullptr;
eCxx::uint32_t  stack_thunk_refcnt = 0;


// Largest stack usage seen in the wild at 6120
#define _stackSize  (6200 / 4)
#define _stackPaint 0xDEADBEEF


// Add a reference, and allocate the stack if necessary
extern void stack_thunk_add_ref()
{
    ++stack_thunk_refcnt;
    if(stack_thunk_refcnt != 1) return;

    DBG_MMU_PRINTF( "\nStackThunk malloc(%u)\n", _stackSize * sizeof(uint32_t) );

    HeapSelectDram ephemeral;

    stack_thunk_ptr = reinterpret_cast<eCxx::uint32_t*>( malloc( _stackSize * sizeof(eCxx::uint32_t) ) );
    if(!stack_thunk_ptr) {
        ets_printf("stack_thunk_add_ref() unable to allocate stack\n");
        abort();
    }

    DBG_MMU_PRINTF( "StackThunk stack_thunk_ptr: %p\n", stack_thunk_ptr );

    stack_thunk_top  = stack_thunk_ptr + _stackSize - 1;
    stack_thunk_save = nullptr;

    stack_thunk_repaint();
}


// Drop a reference, and free the stack if no longer used
extern void stack_thunk_del_ref()
{
    if(!stack_thunk_refcnt) return;

    --stack_thunk_refcnt;
    if(stack_thunk_refcnt) return;

    free(stack_thunk_ptr);

    stack_thunk_ptr  = nullptr;
    stack_thunk_top  = nullptr;
    stack_thunk_save = nullptr;
}


extern void stack_thunk_repaint()
{ for(eCxx::uint32_t i = 0; i < _stackSize; ++i) stack_thunk_ptr[i] = _stackPaint; }


extern eCxx::uint32_t stack_thunk_get_refcnt()
{ return stack_thunk_refcnt; }


extern eCxx::uint32_t stack_thunk_get_stack_top()
{ return reinterpret_cast<eCxx::uint32_t>(stack_thunk_top); }


extern eCxx::uint32_t stack_thunk_get_stack_bot()
{ return reinterpret_cast<eCxx::uint32_t>(stack_thunk_ptr); }


extern eCxx::uint32_t stack_thunk_get_cont_sp()
{ return reinterpret_cast<eCxx::uint32_t>(stack_thunk_save); }


// Return the number of bytes ever used since the stack was created */
extern eCxx::uint32_t stack_thunk_get_max_usage()
{
    // No stack == no usage
    if(!stack_thunk_ptr) return 0;

    eCxx::uint32_t cnt = 0;
    for( cnt = 0; (cnt < _stackSize) && (stack_thunk_ptr[cnt] == _stackPaint); ++cnt );

    return 4 * (_stackSize - cnt);
}


// Print the stack from the first used 16-byte chunk to the top, decodable by the exception decoder
extern void stack_thunk_dump_stack()
{
    eCxx::uint32_t* pos = stack_thunk_ptr;

    while (pos < stack_thunk_top) {
        if( (pos[0] != _stackPaint) || (pos[1] != _stackPaint) || (pos[2] != _stackPaint) || (pos[3] != _stackPaint) ) break;
        pos += 4;
    }

    ets_printf("\n>>>stack>>>\n");
    while(pos < stack_thunk_top) {
        ets_printf("%08x : %08x %08x %08x %08x\n", reinterpret_cast<uint32_t>(pos), pos[0], pos[1], pos[2], pos[3]);
        pos += 4;
    }
    ets_printf("<<<stack<<<\n");
}


// Called when the stack overflow is detected by a thunk
// (main memory is corrupted at this point;  do not return)
extern void stack_thunk_fatal_overflow()
{
    ets_printf("stack_thunk_fatal_overflow()\n");
    abort();
}


} // extern "C"
