/********************************************************************************************
 * arch/mips/src/pic32mx/excptmacros.h
 *
 *   Copyright (C) 2011 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ********************************************************************************************/

#ifndef __ARCH_MIPS_SRC_PIC32MX_EXCPTMACROS_H
#define __ARCH_MIPS_SRC_PIC32MX_EXCPTMACROS_H

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

#include <nuttx/config.h>

#include <arch/irq.h>
#include <arch/pic32mx/cp0.h>

#ifdef __ASSEMBLY__

/********************************************************************************************
 * Pre-Processor Definitions
 ********************************************************************************************/

/********************************************************************************************
 * Global Symbols
 ********************************************************************************************/

#if CONFIG_ARCH_INTERRUPTSTACK > 3
	.global		g_intstackbase
#ifdef CONFIG_PIC32MX_NESTED_INTERRUPTS
	.global		g_nestlevel
#endif
#endif

/********************************************************************************************
 * Assembly Language Macros
 ********************************************************************************************/

/********************************************************************************************
 * General Usage Example:
 *
 * my_exception:
 *		EXCPT_PROLOGUE t0			- Save registers on stack, enable nested interrupts
 *		move a0, sp					- Pass register save structure as the parameter 1
 *		USE_INTSTACK t0, t1, t2		- Switch to the interrupt stack
 *		jal	handler					- Handle the exception IN=old regs OUT=new regs
 *		di							- Disable interrupts
 *		RESTORE_STACK t0, t1		- Undo the operations of USE_STACK
 *		EXCPT_EPILOGUE v0			- Return to the context returned by handler()
 *
 ********************************************************************************************/
/********************************************************************************************
 * Name: EXCPT_PROLOGUE
 *
 * Description:
 *   Provides the "prologue" logic that should appear at the beginning of every exception
 *   handler.
 *
 * On Entry:
 *   sp - Points to the top of the stack
 *   tmp - Is a register the can be modified for scratch usage (after it has been saved)
 *   k0 and k1 - Since we are in an exception handler, these are available for use
 *
 * At completion:
 *   Register state is saved on the stack; All registers are available for usage except sp
 *   and k1:
 *
 *   - sp points the beginning of the register save area
 *   - k1 holds the value of the STATUS register
 *
 *   The following registers are modified: k0, k1, sp, a0
 *
 ********************************************************************************************/

	.macro	EXCPT_PROLOGUE, tmp
	.set	noat

	/* Get the SP from the previous shadow set */

#if 0
	rdpgpr	sp, sp
#endif

	/* "When entering the interrupt handler routine, the interrupt controller must first
	 *  save the current priority and exception PC counter from Interrupt  Priority (IPL)
	 *  bits (Status<15:10>) and the ErrorEPC register, respectively, on the stack. ..."
	 */

#ifdef CONFIG_PIC32MX_NESTED_INTERRUPTS // Does not work!
	mfc0	k0, MIPS32_CP0_CAUSE
	mfc0 	k1, MIPS32_CP0_EPC

	/* Isolate the pending interrupt level in bits 0-5 of k0 */

	srl		k0, k0, CP0_CAUSE_IP_SHIFT

	/* Create the register context stack frame large enough to hold the entire register save
	 * array.
	 */

	addiu	sp, sp, -XCPTCONTEXT_SIZE

	/* Save the EPC and STATUS in the register context array */

	sw		k1, REG_EPC(sp)
	mfc0	k1, MIPS32_CP0_STATUS
	sw		k1, REG_STATUS(sp)

	/* Then insert pending interrupt level as the current mask level in the CP0 status
	 * register. Also clear bits 1-4 in new value of the status register:
	 *
	 *   Bit 1: Exception Level
	 *   Bit 2: Error Level
	 *   Bit 3: (not used in PIC32MX)
	 *   Bit 4: Operating mode == USER
	 */

	ins 	k1, k0, CP0_STATUS_IPL_SHIFT, 6
	ins		k1, zero, 1, 4

	/* And Enable interrupts */

	mtc0	k1, MIPS32_CP0_STATUS
#else
	/* Get the EPC and STATUS register (Don't bother with the CAUSE register if we are
	 * not supporting nested interrupts)
	 */

	mfc0 	k0, MIPS32_CP0_EPC
	mfc0	k1, MIPS32_CP0_STATUS

	/* Create the register context stack frame large enough to hold the entire register
	 * save array.
	 */

	addiu	sp, sp, -XCPTCONTEXT_SIZE

	/* Save the EPC and STATUS in the register context array */

	sw		k0, REG_EPC(sp)
	sw		k1, REG_STATUS(sp)
#endif

	/* Save floating point registers */

	mfhi	k0
	sw		k0, REG_MFHI(sp)
	mflo	k0
	sw		k0, REG_MFLO(sp)

	/* Save general purpose registers */
	/* $1: at_reg, assembler temporary */

	sw		$1, REG_AT(sp)

	/* $2-$3 = v0-v1: Return value registers */

	sw		v0, REG_V0(sp)
	sw		v1, REG_V1(sp)

	/* $4-$7 = a0-a3: Argument registers */

	sw		a0, REG_A0(sp)
	sw		a1, REG_A1(sp)
	sw		a2, REG_A2(sp)
	sw		a3, REG_A3(sp)

	/* $8-$15 = t0-t7: Volatile registers */

	sw		t0, REG_T0(sp)
	sw		t1, REG_T1(sp)
	sw		t2, REG_T2(sp)
	sw		t3, REG_T3(sp)
	sw		t4, REG_T4(sp)
	sw		t5, REG_T5(sp)
	sw		t6, REG_T6(sp)
	sw		t7, REG_T7(sp)

	/* $16-$23 = s0-s7: Static registers */

	sw		s0, REG_S0(sp)
	sw		s1, REG_S1(sp)
	sw		s2, REG_S2(sp)
	sw		s3, REG_S3(sp)
	sw		s4, REG_S4(sp)
	sw		s5, REG_S5(sp)
	sw		s6, REG_S6(sp)
	sw		s7, REG_S7(sp)

	/* $24-25 = t8-t9: More Volatile registers */

	sw		t8, REG_T8(sp)
	sw		t9, REG_T9(sp)

	/* $26-$27 = ko-k1: Reserved for use in exeption handers.  These do not need to be
	 * saved.
	 *
	 * $28 = gp: Only needs to be saved under conditions where there are multiple, per-
	 * thread values for the GP.
	 */

#ifdef MIPS32_SAVE_GP
	sw		gp, REG_GP(sp)
#endif

	/* $30 = either s8 or fp:  Depends if a frame pointer is used or not */

	sw		s8, REG_S8(sp)

	/* $31 = ra: Return address */

	sw		ra, REG_RA(sp)

	/* $29 = sp:  The value of the stack pointer on return from the exception.  a0 is
	 * used as a temporary
	 */

	addiu	\tmp, sp, XCPTCONTEXT_SIZE
	sw		\tmp, REG_SP(sp)
	.endm

/********************************************************************************************
 * Name: EXCPT_EPILOGUE
 *
 * Description:
 *   Provides the "epilogue" logic that should appear at the end of every exception handler.
 *
 * On input:
 *   regs - points to the register save structure.  NOTE:  This *may not* be an address
 *          lying in a stack!  It might be an address in a TCB!
 *   Interrupts are disabled (via 'di')
 *
 * On completion:
 *   All registers restored
 *	 eret is executed to return from the exception
 *
 ********************************************************************************************/

	.macro	EXCPT_EPILOGUE, regs
	.set	noat

	/* Since interrupts are disabled via di can now use k0 and k1 again.  Use k1 as the
	 * pointer to the register save array.
	 */

	move	k1, \regs

	/* Restore the floating point register state */

	lw		k0, REG_MFLO(k1)
	mtlo	k0
	lw		k0, REG_MFHI(k1)
	mthi	k0

	/* Restore general purpose registers */
	/* $1: at_reg, assembler temporary */

	lw		$1, REG_AT(k1)

	/* $2-$3 = v0-v1: Return value registers */

	lw		v0, REG_V0(k1)
	lw		v1, REG_V1(k1)

	/* $4-$7 = a0-a3: Argument registers */

	lw		a0, REG_A0(k1)
	lw		a1, REG_A1(k1)
	lw		a2, REG_A2(k1)
	lw		a3, REG_A3(k1)

	/* $8-$15 = t0-t7: Volatile registers */

	lw		t0, REG_T0(k1)
	lw		t1, REG_T1(k1)
	lw		t2, REG_T2(k1)
	lw		t3, REG_T3(k1)
	lw		t4, REG_T4(k1)
	lw		t5, REG_T5(k1)
	lw		t6, REG_T6(k1)
	lw		t7, REG_T7(k1)

	/* $16-$23 = s0-s7: Static registers */

	lw		s0, REG_S0(k1)
	lw		s1, REG_S1(k1)
	lw		s2, REG_S2(k1)
	lw		s3, REG_S3(k1)
	lw		s4, REG_S4(k1)
	lw		s5, REG_S5(k1)
	lw		s6, REG_S6(k1)
	lw		s7, REG_S7(k1)

	/* $24-25 = t8-t9: More Volatile registers */

	lw		t8, REG_T8(k1)
	lw		t9, REG_T9(k1)

	/* $26-$27 = ko-k1: Reserved for use in exeption handers.  These do not need to be
	 * saved.
	 *
	 * $28 = gp: Only needs to be saved under conditions where there are multiple, per-
	 * thread values for the GP.
	 */

#ifdef MIPS32_SAVE_GP
	lw		gp, REG_GP(k1)
#endif

	/* $29 = sp: Stack pointer */

	lw		sp, REG_SP(k1)

	/* $30 = either s8 or fp:  Depends if a frame pointer is used or not */

	lw		s8, REG_S8(k1)

	/* $31 = ra: Return address */

	lw		ra, REG_RA(k1)

	/* Finally, restore CP status and the EPC */

	lw		k0, REG_STATUS(k1)
	lw		k1, REG_EPC(k1)
	mtc0	k0, MIPS32_CP0_STATUS
	ehb
	mtc0 	k1, MIPS32_CP0_EPC
	eret 
	nop
	.endm

/********************************************************************************************
 * Name: USE_INTSTACK
 *
 * Description:
 *   Switch to the interrupt stack (if enabled in the configuration).
 *
 * On Entry:
 *   sp - Current value of the user stack pointer
 *   tmp1, tmp2, and tmp3 are registers that can be used temporarily.
 *   All interrupts should still be disabled.
 *
 * At completion:
 *   If the nesting level is 0, then (1) the user stack pointer is saved at the base of the
 *   interrupt stack and sp points to the interrupt stack.
 *   The values of tmp1, tmp2, tmp3, and sp have been altered
 *
 ********************************************************************************************/

	.macro	USE_INTSTACK, tmp1, tmp2, tmp3

#if CONFIG_ARCH_INTERRUPTSTACK > 3
#ifdef CONFIG_PIC32MX_NESTED_INTERRUPTS

	/* Check the nesting level.  If there are no nested interrupts, then we can
	 * claim the interrupt stack.
	 */

	la		\tmp1, g_nestlevel
	lw		\tmp2, (\tmp1)
	bne		1f
	nop
#endif

	/* Use the interrupt stack, pushing the user stack pointer onto the interrupt
	 * stack first.
	 */

	la		\tmp3, g_intstackbase
	lw		\tmp, (\tmp3)
	sw		sp, (\tmp3)
	move	sp, \tmp3

#ifdef CONFIG_PIC32MX_NESTED_INTERRUPTS
1:
	/* Increment the interrupt nesting level */

	addiu	\tmp2, \tmp2, 1
	sw		\tmp2, 0(\tmp1)
#endif
#endif
	.endm

/********************************************************************************************
 * Name: RESTORE_STACK
 *
 * Description:
 *   Restore the user stack.  Not really.. actually only decrements the nesting level.  We
 *   always get the new stack pointer for the register save array.
 *
 * On Entry:
 *   tmp1 and tmp2 are registers that can be used temporarily.
 *   All interrupts must be disabled.
 *
 * At completion:
 *   Current nesting level is decremented
 *   The values of tmp1 and  tmp2 have been altered
 *
 ********************************************************************************************/

	.macro		RESTORE_STACK, tmp1, tmp2

#if CONFIG_ARCH_INTERRUPTSTACK > 3
#ifdef CONFIG_PIC32MX_NESTED_INTERRUPTS

	/* Decrement the nesting level */

	la		\tmp1, g_nestlevel
	lw		\tmp2, (\tmp1)
	addiu	\tmp2, \tmp2, -1
	sw		\tmp2, 0(\tmp1)

#endif
#endif
	.endm

#endif /* __ASSEMBLY__ */
#endif /* __ARCH_MIPS_SRC_PIC32MX_EXCPTMACROS_H */
