/*
 * Copyright (c) 2013-2015, Google Inc. All rights reserved
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <asm.h>
#include <lib/sm/monitor.h>
#include <lib/sm/smcall.h>
#include <lib/sm/sm_err.h>

#include <kernel/vm.h>

/*
 * Monitor stacks:
 *  secure context
 *  non-secure context
 *
 * Context:
 *  mon_lr
 *  usr/sys_sp, usr/sys_lr
 *  und_spsr, und_sp, und_lr
 *  abt_spsr, abt_sp, abt_lr
 *  svc_spsr, svc_sp, svc_lr
 *  irq_spsr, irq_sp, irq_lr
 *  fiq_spsr, fiq_r8-fiq_lr
 *  r4-r11, lr
 *  r0-r3, ip
 *  lr, spsr
 *
 */

#define CONTEXT_SIZE_RETURN (2 * 4)
#define CONTEXT_SIZE_SCRATCH_GEN_REGS ((5 + 9) * 4)
#define CONTEXT_SIZE_MODE_REGS_MON_RETURN ((8 + 3 + 3 + 3 + 3 + 2 + 1) * 4)
#define CONTEXT_SIZE (CONTEXT_SIZE_RETURN + CONTEXT_SIZE_SCRATCH_GEN_REGS + CONTEXT_SIZE_MODE_REGS_MON_RETURN)
#define MONITOR_STACK_SIZE (CONTEXT_SIZE * 2)

.macro SAVE_CONTEXT_RETURN
	srsdb	sp!, #MODE_MON	/* srsfd alias not recognized by current assembler */
.endm

.macro SAVE_CONTEXT_GEN_REGS
	push	{r4-r11, lr}
.endm

.macro SAVE_CONTEXT_SCRATCH_REGS
	push	{r0-r3, ip}
.endm

.macro SAVE_CONTEXT
	SAVE_CONTEXT_RETURN
	SAVE_CONTEXT_SCRATCH_REGS
	SAVE_CONTEXT_GEN_REGS
.endm

.macro SAVE_CONTEXT_MODE_REGS
	mov	r4, sp

	cps	#MODE_FIQ
	mrs	r5, spsr
	stmfd	r4!, {r5, r8-lr}

	cps	#MODE_IRQ
	mrs	r5, spsr
	stmfd	r4!, {r5, sp, lr}

	cps	#MODE_SVC
	mrs	r5, spsr
	stmfd	r4!, {r5, sp, lr}

	cps	#MODE_ABT
	mrs	r5, spsr
	stmfd	r4!, {r5, sp, lr}

	cps	#MODE_UND
	mrs	r5, spsr
	stmfd	r4!, {r5, sp, lr}

	cps	#MODE_SYS
	stmfd	r4!, {sp, lr}

	cps	#MODE_MON
	mov	sp, r4
.endm

.macro SAVE_RETURN_ADDR reg
	push	{\reg}
.endm

.macro RESTORE_RETURN_ADDR reg
	pop	{\reg}
.endm

.macro RESTORE_CONTEXT_MODE_REGS
	mov	r4, sp

	cps	#MODE_SYS
	ldmfd	r4!, {sp, lr}

	cps	#MODE_UND
	ldmfd	r4!, {r5, sp, lr}
	msr	spsr, r5

	cps	#MODE_ABT
	ldmfd	r4!, {r5, sp, lr}
	msr	spsr, r5

	cps	#MODE_SVC
	ldmfd	r4!, {r5, sp, lr}
	msr	spsr, r5

	cps	#MODE_IRQ
	ldmfd	r4!, {r5, sp, lr}
	msr	spsr, r5

	cps	#MODE_FIQ
	ldmfd	r4!, {r5, r8-lr}
	msr	spsr, r5

	cps	#MODE_MON
	mov	sp, r4
.endm

.macro RESTORE_CONTEXT_GEN_REGS
	pop	{r4-r11, lr}
.endm

.macro RESTORE_CONTEXT_SCRATCH_REGS
	pop	{r0-r3, ip}
.endm

.macro RESTORE_CONTEXT_AND_RETURN
	RESTORE_CONTEXT_GEN_REGS
	add	sp, sp, #5 * 4
	rfefd	sp!
.endm

.p2align 5
.globl monitor_vector_table
monitor_vector_table:
	nop				/* RESET	*/
	b	.			/* UNDEF	*/
	b	.Lmon_smcall		/* SWI		*/
	b	.			/* IABORT	*/
	b	.			/* DABORT	*/
	nop				/* reserved	*/
	b	.			/* IRQ		*/
	b	.Lmon_fiq_entry		/* FIQ		*/

/*
 * Called in monitor mode
 * return address in mon-lr
 * ns-return address in svc-lr
 */

FUNCTION(monitor_reset)
	/* figure out our cpu number */
	mrc     p15, 0, r11, c0, c0, 5 /* read MPIDR */

	/* mask off the bottom 12 bits to test cluster number:cpu number */
	ubfx    r11, r11, #0, #12

	/* return if cpu >= SMP_MAX_CPUS */
	cmp	r11, #SMP_MAX_CPUS
	bxge	lr

	/* find cpu specifc stack offset */
	mov	ip, #MONITOR_STACK_SIZE
	mul	r11, r11,ip

	adr	sp, monitor_reset	/* sp = paddr */
	ldr	ip, =monitor_reset	/* ip = vaddr */
	sub	ip, sp, ip		/* ip = phys_offset */
	ldr	sp, =mon_ns_stack_top	/* sp = ns_stack vaddr */
	add	sp, sp, ip		/* sp = ns_stack paddr */

	/* apply per-cpu stack offset */
	sub	sp, sp, r11

	/* Save non-secure return address, mode and registers */
	cps	#MODE_SVC
	msr	spsr_cfsx, #MODE_SVC_IRQ_DISABLED
	SAVE_CONTEXT_RETURN
	cps	#MODE_MON

	SAVE_CONTEXT_SCRATCH_REGS
	SAVE_CONTEXT_GEN_REGS
	SAVE_CONTEXT_MODE_REGS
	ldr	r4, =platform_mon_initial_ns_return
	SAVE_RETURN_ADDR r4

	sub	sp, sp, ip	/* sp = sp vaddr */

	/* Initialize NS mode CPU context registers */
	mrc	p15, 0, r4, c12, c0, 0	/* r4 = VBAR */
	mrc	p15, 0, r5, c2, c0, 0	/* r5 = TTBR0 */
	mrc	p15, 0, r6, c2, c0, 1	/* r6 = TTBR1 */
	mrc	p15, 0, r7, c3, c0, 0	/* r7 = DACR */
	mrc	p15, 0, r8, c1, c0, 0	/* r8 = SCTLR */

	SWITCH_SCR_TO_NONSECURE r9

	mcr	p15, 0, r4, c12, c0, 0	/* VBAR = r4 */
	mcr	p15, 0, r5, c2, c0, 0	/* TTBR0 = r5 */
	mcr	p15, 0, r6, c2, c0, 1	/* TTBR1 = r6 */
	mcr	p15, 0, r7, c3, c0, 0	/* DACR = r7 */
	mcr	p15, 0, r8, c1, c0, 0	/* SCTLR = r8 */

	SWITCH_SCR_TO_SECURE r9

	bx	lr

FUNCTION(monitor_init_mmu_on)
	bx	lr

.Lmon_invalid_ns_return_addr:
	b	.

FUNCTION(mon_initial_ns_return)
	cmp	lr, #~0
	strne	lr, [sp, #CONTEXT_SIZE_SCRATCH_GEN_REGS]
	b	.Lmon_smcall_initial_ns_return

/* direction in flags */
.Lmon_smcall_from_secure:
	adreq	lr, .Lmon_smcall_secure_return
.Lmon_context_switch:
	SAVE_CONTEXT_MODE_REGS
	SAVE_RETURN_ADDR lr

	addeq	sp, sp, #(CONTEXT_SIZE)	/* secure => non-secure */
	subne	sp, sp, #(CONTEXT_SIZE)	/* non-secure => secure */

	RESTORE_RETURN_ADDR lr
	RESTORE_CONTEXT_MODE_REGS
	bx	lr

.Lmon_smcall:
	SAVE_CONTEXT

	mrc	p15, 0, r4, c1, c1, 0	/* r4 = SCR */
	tst	r4, #0x1
	beq	.Lmon_smcall_from_secure

	SWITCH_SCR_TO_SECURE r4
	bl	.Lmon_context_switch
.Lmon_smcall_initial_ns_return:
.weak platform_mon_initial_ns_return
platform_mon_initial_ns_return:
	RESTORE_CONTEXT_GEN_REGS

	ldr	lr, [sp]

	/* Handle SMC_FC_FIQ_EXIT from non-secure */
	cmp	lr, #SMC_FC_FIQ_EXIT
	beq	.Lmon_fiq_exit_return

	SWITCH_SCR_TO_NONSECURE r3
	mov	r0, r1	/* secure os passes the return code in r1 */

	add	sp, sp, #5 * 4
	rfefd	sp!


.Lmon_smcall_secure_return:
	RESTORE_CONTEXT_AND_RETURN

.Lmon_fiq_entry:

	SAVE_CONTEXT

	mrc	p15, 0, r4, c1, c1, 0	/* r4 = SCR */
	tst	r4, #0x1
	beq	.	/* fiqs are not trapped in secure mode */

	ldr	r0, =SMC_FC_FIQ_ENTER

	SWITCH_SCR_TO_SECURE r4
	bl	.Lmon_context_switch
	SWITCH_SCR_TO_NONSECURE r4

	RESTORE_CONTEXT_GEN_REGS
	ldr	lr, [sp, #(5 * 4)]

	cmp	r1, #0	/* secure os passes the return code in r1 */
	subne	lr, lr, #4
	bne	.Lmon_fiq_return

	ldr	r0, [sp, #(6 * 4)] /* load saved spsr */
	mov	r1, lr

	mov	r2, #0x91 /* fiq mode with IRQ disabled */
	str	r2, [sp, #(6 * 4)] /* store spsr */

	mrc	p15, 0, r3, c1, c0, 0	/* r3 = Non-secure SCTLR */
	tst	r3, #(1 << 13) /* Non-secure SCTLR.V */
	ldrne	lr, =0xffff0000
	mrceq	p15, 0, lr, c12, c0, 0 /* lr = VBAR (if not using Hivecs) */
	add	lr, lr, #0x1c /* fiq vector offset */

	SWITCH_SCR_TO_SECURE r2

	/* Set fiq mode lr and spsr */
	cps	#MODE_FIQ
	msr	spsr_cfsx, r0
	mov	lr, r1
	cps	#MODE_MON

	SWITCH_SCR_TO_NONSECURE r2

.Lmon_fiq_return:
	str	lr, [sp, #(5 * 4)]
	RESTORE_CONTEXT_SCRATCH_REGS
	rfefd	sp!

.Lmon_fiq_exit_return:
	/* Retrieve FIQ mode spsr, lr and restore r0 from FIQ mode r12 */
	cps	#MODE_FIQ

	mov	r0, r12 /* restore r0 used for smc number */
	mrs	r1, spsr
	mov	r2, lr

	cps	#MODE_MON

	str	r2, [sp, #(5 * 4)] /* save return addr */

	/* don't allow return to monitor mode */
	and	r2, r1, #0x1f
	cmp	r2, #MODE_MON
	moveq	r1, #MODE_UND

	str	r1, [sp, #(6 * 4)] /* store spsr */

	SWITCH_SCR_TO_NONSECURE r3

	mov	lr, r0
	RESTORE_CONTEXT_SCRATCH_REGS
	mov	r0, lr
	rfefd	sp!

.data
.align 3

/* Monitor stack for primary cpu */
LOCAL_DATA(mon_ns_stack)
	.skip	MONITOR_STACK_SIZE * SMP_MAX_CPUS
LOCAL_DATA(mon_ns_stack_top)