/************************************************************************
*
*             Copyright Mentor Graphics Corporation 2013
*                         All Rights Reserved.
*
* THIS WORK CONTAINS TRADE SECRET AND PROPRIETARY INFORMATION WHICH IS
* THE PROPERTY OF MENTOR GRAPHICS CORPORATION OR ITS LICENSORS AND IS
* SUBJECT TO LICENSE TERMS.
*
*************************************************************************

*************************************************************************
*
* FILE NAME
*
*       arm_proc_mode.c
*
* COMPONENT
*
*       SU - Supervisor/User mode switching support
*
* DESCRIPTION
*
*       This file contains target specific implementations of mode
*       switching routines.
*
* DATA STRUCTURES
*
*       None
*
* FUNCTIONS
*
*       PROC_AR_User_Mode
*       PROC_AR_Software_Interrupt
*       PROC_AR_Trampoline_Return
*
* DEPENDENCIES
*
*       nucleus.h
*       nu_kernel.h
*       proc_core.h
*
***********************************************************************/
#include "nucleus.h"
#include "kernel/nu_kernel.h"
#include "os/kernel/process/core/proc_core.h"

/***********************************************************************
*
* FUNCTION
*
*      PROC_AR_User_Mode
*
* DESCRIPTION
*
*      This function performs the actual mode switch on the hardware to
*      user mode.
*
* INPUTS
*
*      None
*
* OUTPUTS
*
*      None
*
***********************************************************************/
VOID PROC_AR_User_Mode(VOID)
{
    UNSIGNED cpsr_tmp;

    /* Read the Current Program Status Register */
    ESAL_TS_RTE_CPSR_CXSF_READ(&cpsr_tmp);

    /* Disable interrupts for critical section */
    ESAL_GE_INT_FAST_ALL_DISABLE();

    /* Clear the mode flag */
    TCD_Execute_Task -> tc_return_addr = NU_NULL;

#ifdef CFG_NU_OS_KERN_PROCESS_MEM_MGMT_ENABLE
    if (PROC_Scheduled_CB -> kernel_mode == NU_FALSE)
    {
        /* Restore the domain access to client
           before switch to user mode */
        ESAL_AR_SET_DOMAIN(ESAL_AR_DACR_CLIENT);
        ESAL_AR_INVALIDATE_TLB();
    }

#endif

#if (CFG_NU_OS_KERN_PROCESS_CORE_SUP_USER_MODE == NU_FALSE)
    /* Restore previous cpsr value */
    ESAL_TS_RTE_CPSR_CXSF_WRITE(cpsr_tmp);
#else
    /* Switch to user mode (clear bottom 4-bits) */
    ESAL_TS_RTE_CPSR_CXSF_WRITE(cpsr_tmp & 0xFFFFFFF0);
#endif
}

/*************************************************************************
*
* FUNCTION
*
*      PROC_AR_Software_Interrupt
*
* DESCRIPTION
*
*      This exception handler causes a transition to Supervisor mode.
*      Before making the switch it will verify that the caller is part
*      of the kernel.
*
* INPUTS
*
*      exception_num
*      stack_frame
*
* OUTPUTS
*
*      None
*
*************************************************************************/
VOID PROC_AR_Software_Interrupt(INT exception_num, VOID *stack_frame)
{
    ESAL_AR_STK_MIN *stk_frame = (ESAL_AR_STK_MIN *) stack_frame;

    /* Stay in supervisor mode disable FIQ and IRQ
       interrupts during critical section.*/
    ESAL_TS_RTE_CPSR_C_WRITE(ESAL_AR_INT_CPSR_SUP_MODE | ESAL_AR_INTERRUPTS_DISABLE_BITS);

    /* Ensure service call only occurs in kernel space */
    if ((stk_frame->rtn_address >= (UINT32)TEXT_START) &&
        (stk_frame->rtn_address < (UINT32)TEXT_END))
    {
#ifdef CFG_NU_OS_KERN_PROCESS_MEM_MGMT_ENABLE

        /* Manager access */
        ESAL_AR_SET_DOMAIN(ESAL_AR_DACR_MANAGER);

#endif

#if (CFG_NU_OS_KERN_PROCESS_CORE_SUP_USER_MODE == NU_TRUE)
        /* Set System mode in SPSR */
        stk_frame->spsr  |= PROC_AR_SYS_MODE;
#endif

        /* Save function return address in TCB */
        TCD_Execute_Task->tc_return_addr = (VOID *)stk_frame->r12;
    }
}

/*************************************************************************
*
* PUBLIC FUNCTION
*
*      PROC_AR_Trampoline_Return
*
* DESCRIPTION
*
*      This routine contains the "common" code that will return to a
*      process after a service call.  It is the "bottom" half of the
*      trampoline functionality used to go between a process and the
*      kernel
*
* INPUTS
*
*      None
*
* OUTPUTS
*
*      None
*
*************************************************************************/
VOID    PROC_AR_Trampoline_Return(VOID)
{
    asm volatile(/* Get return address from the thread control block */
                 " LDR  r3,=TCD_Current_Thread\n"
                 " LDR  r3,[r3]\n"
                 " LDR  r3,[r3,#%c[return_addr]]\n"

                 /* Save registers on stack keeping 8-byte stack alignment */
                 " PUSH {r0-r3}\n"

                 /* Call function to switch back to user mode / perform MMU ops */
                 " BLX  PROC_AR_User_Mode\n"

                 /* Restore registers and return to caller */
                 " POP  {r0-r2,lr}\n"
                 " BX   lr\n"
                 :
                 : [return_addr] "i" (offsetof(TC_TCB, tc_return_addr)));
}
