/*=============================================================================
//
//      icontext.c
//
//      SPARClite HAL context init function
//
//=============================================================================
//####COPYRIGHTBEGIN####
//
// -------------------------------------------
// The contents of this file are subject to the Cygnus eCos Public License
// Version 1.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://sourceware.cygnus.com/ecos
// 
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the
// License for the specific language governing rights and limitations under
// the License.
// 
// The Original Code is eCos - Embedded Cygnus Operating System, released
// September 30, 1998.
// 
// The Initial Developer of the Original Code is Cygnus.  Portions created
// by Cygnus are Copyright (C) 1998,1999 Cygnus Solutions.  All Rights Reserved.
// -------------------------------------------
//
//####COPYRIGHTEND####
//=============================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):   hmt
// Contributors:        hmt
// Date:        1998-12-14
// Purpose:     HAL context initialization function
// Description: Initialize a HAL context for SPARClite; this is in C and out
//              of line because there is too much of it for a simple macro.
//
//####DESCRIPTIONEND####
//
//===========================================================================*/

#include <pkgconf/hal.h>

#include <cyg/hal/hal_arch.h>           // HAL header

#include <cyg/infra/cyg_type.h>

#include <cyg/hal/vectors.h>            // SAVE_REGS_SIZE, __WINSIZE, ...

/*---------------------------------------------------------------------------*/

/* We lay out the stack in the manner that the PCS demands:
 *        frame pointer -----> [top of stack] 
 *                             Argument spill area (6 words)
 *                             Return Arg pointer
 *                             Initial saved register window (i[8], l[8])
 *                                for use by program when it starts
 *                             [rest of] saved register object (various)
 *        stack pointer -----> saved register window (i[8], l[8])
 *                                to allow us to be interrupted.
 *
 * ie.    frame pointer ->
 *                          struct HAL_FrameStructure
 *        stack pointer ->  struct HAL_SavedRegisters
 *
 * and when the context "resumes" sp is incremented by 40 * 4, the size of
 * a  _struct HAL_SavedRegisters_  which points it at the extant but unused
 *  _struct HAL_FrameStructure_  as the PCS requires.  The frame pointer is
 * left pointing off the top of stack.
 *
 *
 * Thus the stack is the same if created from an already executing context:
 *
 *        frame pointer -----> 
 *                             [temporaries and locals]
 *                             [more arguments]
 *                             Argument spill area (6 words)
 *                             Return Arg pointer
 *        [sp at entry]------> Previous saved register window (i[8], l[8])
 *                                for use by program when it starts
 *                             [rest of] saved register object (various)
 *        stack pointer -----> saved register window (i[8], l[8])
 *                                to allow us to be interrupted.
 */

CYG_ADDRESS
hal_thread_init_context(  CYG_WORD sparg,
                          CYG_WORD thread,
                          CYG_WORD entry,
                          CYG_WORD id )
{
    register CYG_WORD fp = sparg;
    register CYG_WORD sp = 0;
    register HAL_SavedRegisters *regs;
    register HAL_FrameStructure *frame;
    int i;

    if ( 0 == (id & 0xffff0000) )
        id <<= 16;

    fp &= ~15;                          // round down to double alignment

    frame = (HAL_FrameStructure *)(
        fp - sizeof( HAL_FrameStructure ) );
    
    regs = (HAL_SavedRegisters *)(
        ((CYG_WORD)frame) - sizeof(HAL_SavedRegisters) );

    sp = (CYG_WORD)regs;
    
    for ( i = 0; i < 6; i++ ) {
        frame->spill_args[i] = id | 0xa0 | i;
    }    
    frame->composite_return_ptr = 0;

    for ( i = 0; i < 8; i++ ) {
        frame->li.i[i]   = id | ( 56 + i );
        frame->li.l[i]   = id | ( 48 + i );
        regs ->li.i[i]   = id | ( 24 + i );
        regs ->li.l[i]   = id | ( 16 + i );
        regs ->o[i]      = id | (  8 + i );
        regs ->g[i]      = id | (      i );
    }

    // first terminate the linked list on the stack in the initial
    // (already saved) register window:
    frame->li.i[6] = regs->li.i[6] = (cyg_uint32)fp; // frame pointer
    frame->li.i[7] = regs->li.i[7] = (cyg_uint32)0;  // no ret addr here

    // and set up other saved regs as if called from just before
    // the entry point:
    regs->o[7] = (entry - 8);
    regs->o[6] = sp;

    // this is the argument that the entry point is called with
    regs->o[0] = thread;

    // this is the initial CWP; quite arbitrary really, the WIM is set up
    // accordingly in hal_thread_load_context().

    regs->g[0] = 7;

    return (CYG_ADDRESS)sp;
}

// ---------------------------------------------------------------------------

//#define THREAD_DEBUG_SERIAL_VERBOSE

#ifdef THREAD_DEBUG_SERIAL_VERBOSE         // NOT INCLUDED

// This is unpleasant to try to debug, because these routines are called
// WHEN THE PROGRAM IS NOT RUNNING from the CygMon's GDB stubs - so you
// can't use any normal output: these little routines use the serial
// line directly, so are best used when debugging via Ethernet, so you
// just have a separate output stream to read.  Nasty...

#include <cyg/hal/hal_diag.h>

#undef  HAL_DIAG_WRITE_CHAR
#define HAL_DIAG_WRITE_CHAR(_c_) CYG_MACRO_START                    \
    SLEB_LED = (_c_);                                               \
    HAL_DIAG_WRITE_CHAR_DIRECT( _c_ );                              \
CYG_MACRO_END

static void swritec( char c )
{
    HAL_DIAG_WRITE_CHAR( c );
}

static void swrites( char *s )
{
    char c;
    while ( 0 != (c = *s++) )
        HAL_DIAG_WRITE_CHAR( c );
}

static void swritex( cyg_uint32 x )
{
    int i;
    swrites( "0x" );
    for ( i = 28; i >= 0; i-= 4 ) {
        char c = "0123456789abcdef"[ 0xf & (x >> i) ];
        HAL_DIAG_WRITE_CHAR( c );
    }
}

#define newline() swrites( "\n\r" )

static void x8( char *s, unsigned long *xp )
{
    int i;
    for ( i = 0; i < 8; i++ ) {
        swrites( s );
        swritec( '0' + i );
        swrites( " = " );
        swritex( xp[i] );
        if ( 3 == (i & 3) )
            newline();
        else
            swrites( "    " );
    }
}

#endif // THREAD_DEBUG_SERIAL_VERBOSE ... NOT INCLUDED

// ---------------------------------------------------------------------------
// Routines in icontext.c used here because they're quite large for
// the SPARClite (note param order); these are used in talking to GDB.

enum regnames {G0 = 0, G1, G2, G3, G4, G5, G6, G7,
               O0, O1, O2, O3, O4, O5, SP, O7,
               L0, L1, L2, L3, L4, L5, L6, L7,
               I0, I1, I2, I3, I4, I5, FP, I7,

               F0, F1, F2, F3, F4, F5, F6, F7,
               F8, F9, F10, F11, F12, F13, F14, F15,
               F16, F17, F18, F19, F20, F21, F22, F23,
               F24, F25, F26, F27, F28, F29, F30, F31,
               Y, PSR, WIM, TBR, PC, NPC, FPSR, CPSR};

typedef unsigned long target_register_t;

void
cyg_hal_sparc_get_gdb_regs( void *gdb_regset,
                            HAL_SavedRegisters *eCos_regset )
{
    target_register_t *gdb = (target_register_t *)gdb_regset;
    int reg;
    cyg_uint32 scratch = 0;
    cyg_uint32 *sptrap;
    HAL_SavedWindow *trapli, *ctxli;

    if ( 0 == eCos_regset->g[0]             ||
         0xc0 == (0xe0 & eCos_regset->g[0])    ) {
        // Then it's an interrupt stack saved state:
        // (either minimal, or a saved PSR with traps disabled)
        // The saved register set is pretty minimal, so we have to grub
        // around in the stack to find out some truth...
        sptrap = (cyg_uint32 *)eCos_regset; // point to the IL save area for
        sptrap -= 24;                   // the trap handler, for PC, NPC
        trapli = (HAL_SavedWindow *)sptrap; // Get at those regs

        ctxli = (HAL_SavedWindow *)(trapli->i[6]); // (the frame pointer)
                                        // and get at the interruptee's regs

        // Pick up interruptee's registers from all over the stack:
        for ( reg = 0; reg < 8 ; reg++ ) {
            gdb[ G0 + reg ] = eCos_regset->g[reg];
            gdb[ O0 + reg ] = trapli->i[reg];
            gdb[ L0 + reg ] = ctxli->l[reg];
            gdb[ I0 + reg ] = ctxli->i[reg];
        }
    
        // Clear out G0 which is always 0 (but abused in eCos_regset)
        // and the FP regs which we do not have:
        gdb[ G0 ] = 0;
        for ( reg = F0; reg <= F31; reg++ )
            gdb[ reg ] = 0;
    
        // In the save context _of the trap handler_ registers are as follows:
        // %l0 = psr (with this CWP/window-level in it)
        // %l1 = pc
        // %l2 = npc
        // %l3 = vector number (1-15 for interrupts)
        // %l4 = Y register preserved
        gdb[ Y ]    = trapli->l[4];
        
        scratch = trapli->l[0];         // the PSR in the trap handler
        scratch++;                      // back to interupted thread's window
        scratch &=~ 0x38;               // clear ET and any CWP overflow
        gdb[ PSR ]  = scratch;
        gdb[ WIM ]  = 1 << ((__WINBITS & (1 + scratch)));
    
        // Read _a_ TBR value and ignore the current trap details:
        asm volatile ( "rd %%tbr, %0" : "=r"(scratch) : );
        gdb[ TBR ]  = (scratch &~ 0xfff);
        
        gdb[ PC ]   = trapli->l[1];
        gdb[ NPC ]  = trapli->l[2];
    
        gdb[ FPSR ] = 0;
        gdb[ CPSR ] = 0;

#ifdef THREAD_DEBUG_SERIAL_VERBOSE
        newline();
        swrites( "-----------------------------------------------------" ); newline();
        swrites( "-------------- INTERRUPT STACK GET ------------------" ); newline();
        swrites( "eCos regset at " ); swritex( eCos_regset ); newline();
        swrites( "        trapli " ); swritex( trapli      ); newline();
        swrites( "         ctxli " ); swritex(  ctxli      ); newline();
        x8( "global ", &(gdb[G0]) );
        x8( "    in ", &(gdb[I0]) );
        x8( " local ", &(gdb[L0]) );
        x8( "   out ", &(gdb[O0]) );
        swrites( "gdb  PC = " ); swritex( gdb[  PC ] ); newline();
        swrites( "gdb NPC = " ); swritex( gdb[ NPC ] ); newline();
        swrites( "gdb PSR = " ); swritex( gdb[ PSR ] ); newline();
#endif

    }
    else {
        // It's a synchronous context switch that led to this object.
        // Pick up interruptee's registers from the saved context:
        for ( reg = 0; reg < 8 ; reg++ ) {
#ifdef CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM
            gdb[ G0 + reg ] = 0;
            gdb[ O0 + reg ] = 0;
#else
            gdb[ G0 + reg ] = eCos_regset->g[reg];
            gdb[ O0 + reg ] = eCos_regset->o[reg];
#endif
            gdb[ L0 + reg ] = eCos_regset->li.l[reg];
            gdb[ I0 + reg ] = eCos_regset->li.i[reg];
        }

#ifdef CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM
        // Set up the stack pointer by arithmetic and the return address
        gdb[ SP ] = ((cyg_uint32)(eCos_regset));
        gdb[ O7 ] = eCos_regset->o[ 7 ];
#else
        // Clear out G0 which is always 0 (but abused in eCos_regset)
        gdb[ G0 ] = 0;
#endif
        // and clear the FP regs which we do not have:
        for ( reg = F0; reg <= F31; reg++ )
            gdb[ reg ] = 0;
    
        gdb[ Y ]    = 0;                // it's not preserved.
        
        scratch = eCos_regset->g[ 0 ];  // the PSR in the saved context
        gdb[ PSR ]  = scratch;          // return it verbatim.
        gdb[ WIM ]  = 1 << ((__WINBITS & (1 + scratch)));
    
        // Read _a_ TBR value and ignore the current trap details:
        asm volatile ( "rd %%tbr, %0" : "=r"(scratch) : );
        gdb[ TBR ]  = (scratch &~ 0xfff);
        
        gdb[ PC ]   = eCos_regset->o[ 7 ]; // the return address
        gdb[ NPC ]  = 4 + gdb[ PC ];
    
        gdb[ FPSR ] = 0;
        gdb[ CPSR ] = 0;

#ifdef THREAD_DEBUG_SERIAL_VERBOSE
        newline();
        swrites( "-----------------------------------------------------" ); newline();
        swrites( "-------------- SYNCHRONOUS SWITCH GET----------------" ); newline();
        swrites( "eCos regset at " ); swritex( eCos_regset ); newline();
        x8( "global ", &(gdb[G0]) );
        x8( "    in ", &(gdb[I0]) );
        x8( " local ", &(gdb[L0]) );
        x8( "   out ", &(gdb[O0]) );
        swrites( "gdb  PC = " ); swritex( gdb[  PC ] ); newline();
        swrites( "gdb NPC = " ); swritex( gdb[ NPC ] ); newline();
        swrites( "gdb PSR = " ); swritex( gdb[ PSR ] ); newline();
#endif
    }

}

// ---------------------------------------------------------------------------

void
cyg_hal_sparc_set_gdb_regs( HAL_SavedRegisters *eCos_regset,
                            void *gdb_regset )
{
    target_register_t *gdb = (target_register_t *)gdb_regset;
    int reg;
    cyg_uint32 scratch = 0;
    cyg_uint32 *sptrap;
    HAL_SavedWindow *trapli, *ctxli;

    // Guess where the eCos register set really is:
    if ( 0 == eCos_regset->g[0]             ||
         0xc0 == (0xe0 & eCos_regset->g[0])    ) {
        // Then it's an interrupt stack saved state:
        // (either minimal, or a saved PSR with traps disabled)
        // The saved register set is pretty minimal, so we have to grub
        // around in the stack to find out some truth...
        sptrap = (cyg_uint32 *)eCos_regset; // point to the IL save area for
        sptrap -= 24;                   // the trap handler, for PC, NPC
        trapli = (HAL_SavedWindow *)sptrap; // Get at those regs

        ctxli = (HAL_SavedWindow *)(trapli->i[6]); // (the frame pointer)
                                        // and get at the interruptee's regs

        scratch = eCos_regset->g[0];

        // Put back interruptee's registers all over the stack:
        for ( reg = 0; reg < 8 ; reg++ ) {
            eCos_regset->g[reg] = gdb[ G0 + reg ] ;
            trapli->i[reg]      = gdb[ O0 + reg ] ;
            ctxli->l[reg]       = gdb[ L0 + reg ] ;
            ctxli->i[reg]       = gdb[ I0 + reg ] ;
        }
    
        // Put back the eCos G0 which is always 0 (but abused in eCos_regset)
        eCos_regset->g[0] = scratch;

        // In the save context _of the trap handler_ registers are as follows:
        // %l0 = psr (with this CWP/window-level in it)
        // %l1 = pc
        // %l2 = npc
        // %l3 = vector number (1-15 for interrupts)
        // %l4 = Y register preserved
        trapli->l[4] = gdb[ Y ];
        
        // I am *not* interfering with the saved PSR, nor the TBR nor WIM.
        
        // put back return PC and NPC
        trapli->l[1] = gdb[ PC ] ;
        trapli->l[2] = gdb[ NPC ];
    
#ifdef THREAD_DEBUG_SERIAL_VERBOSE
        newline();
        swrites( "-----------------------------------------------------" ); newline();
        swrites( "-------------- INTERRUPT STACK SET ------------------" ); newline();
        swrites( "eCos regset at " ); swritex( eCos_regset ); newline();
        swrites( "        trapli " ); swritex( trapli      ); newline();
        swrites( "         ctxli " ); swritex(  ctxli      ); newline();
        x8( "global ", &(gdb[G0]) );
        x8( "    in ", &(gdb[I0]) );
        x8( " local ", &(gdb[L0]) );
        x8( "   out ", &(gdb[O0]) );
        swrites( "gdb  PC = " ); swritex( gdb[  PC ] ); newline();
        swrites( "gdb NPC = " ); swritex( gdb[ NPC ] ); newline();
        swrites( "gdb PSR = " ); swritex( gdb[ PSR ] ); newline();
#endif

    }
    else {
        // It's a synchronous context switch that led to this object.
        // Pick up interruptee's registers from the saved context:

        scratch = eCos_regset->g[0];

        for ( reg = 0; reg < 8 ; reg++ ) {
            eCos_regset->g[reg]    = gdb[ G0 + reg ];
            eCos_regset->o[reg]    = gdb[ O0 + reg ];
            eCos_regset->li.l[reg] = gdb[ L0 + reg ];
            eCos_regset->li.i[reg] = gdb[ I0 + reg ];
        }

        // Put back the eCos G0 which is always 0 (but abused in eCos_regset)
        eCos_regset->g[0] = scratch;
        
        // I am *not* interfering with the saved PSR, nor the TBR nor WIM.

        // The PC is in o7, altering it via GDB's PC is not on.
        // Setting the NPC in a voluntary context is meaningless.

#ifdef THREAD_DEBUG_SERIAL_VERBOSE
        newline();
        swrites( "-----------------------------------------------------" ); newline();
        swrites( "-------------- SYNCHRONOUS SWITCH SET ---------------" ); newline();
        swrites( "eCos regset at " ); swritex( eCos_regset ); newline();
        x8( "global ", &(gdb[G0]) );
        x8( "    in ", &(gdb[I0]) );
        x8( " local ", &(gdb[L0]) );
        x8( "   out ", &(gdb[O0]) );
        swrites( "gdb  PC = " ); swritex( gdb[  PC ] ); newline();
        swrites( "gdb NPC = " ); swritex( gdb[ NPC ] ); newline();
        swrites( "gdb PSR = " ); swritex( gdb[ PSR ] ); newline();
#endif
    }

}

/*---------------------------------------------------------------------------*/
// EOF icontext.c
