/* SPDX-License-Identifier: GPL-2.0-or-later */

#include <asm/asm-offsets.h>
#include <asm/bug.h>
#include <asm/page.h>
#include <asm/ppc_asm.h>

/*
 * RTAS is called with MSR IR, DR, EE disabled, and LR in the return address.
 *
 * Note: r3 is an input parameter to rtas, so don't trash it...
 */

#ifdef CONFIG_PPC32
_GLOBAL(enter_rtas)
	stwu	r1,-INT_FRAME_SIZE(r1)
	mflr	r0
	stw	r0,INT_FRAME_SIZE+4(r1)
	LOAD_REG_ADDR(r4, rtas)
	lis	r6,1f@ha	/* physical return address for rtas */
	addi	r6,r6,1f@l
	tophys(r6,r6)
	lwz	r8,RTASENTRY(r4)
	lwz	r4,RTASBASE(r4)
	mfmsr	r9
	stw	r9,8(r1)
	li	r9,MSR_KERNEL & ~(MSR_IR|MSR_DR)
	mtlr	r6
	stw	r1, THREAD + RTAS_SP(r2)
	mtspr	SPRN_SRR0,r8
	mtspr	SPRN_SRR1,r9
	rfi
1:
	lis	r8, 1f@h
	ori	r8, r8, 1f@l
	LOAD_REG_IMMEDIATE(r9,MSR_KERNEL)
	mtspr	SPRN_SRR0,r8
	mtspr	SPRN_SRR1,r9
	rfi			/* Reactivate MMU translation */
1:
	lwz	r8,INT_FRAME_SIZE+4(r1)	/* get return address */
	lwz	r9,8(r1)	/* original msr value */
	addi	r1,r1,INT_FRAME_SIZE
	li	r0,0
	stw	r0, THREAD + RTAS_SP(r2)
	mtlr	r8
	mtmsr	r9
	blr			/* return to caller */
_ASM_NOKPROBE_SYMBOL(enter_rtas)

#else /* CONFIG_PPC32 */
#include <asm/exception-64s.h>

/*
 * 32-bit rtas on 64-bit machines has the additional problem that RTAS may
 * not preserve the upper parts of registers it uses.
 */
_GLOBAL(enter_rtas)
	mflr	r0
	std	r0,16(r1)
	stdu	r1,-SWITCH_FRAME_SIZE(r1) /* Save SP and create stack space. */

	/* Because RTAS is running in 32b mode, it clobbers the high order half
	 * of all registers that it saves.  We therefore save those registers
	 * RTAS might touch to the stack.  (r0, r3-r12 are caller saved)
	 */
	SAVE_GPR(2, r1)			/* Save the TOC */
	SAVE_NVGPRS(r1)			/* Save the non-volatiles */

	mfcr	r4
	std	r4,_CCR(r1)
	mfctr	r5
	std	r5,_CTR(r1)
	mfspr	r6,SPRN_XER
	std	r6,_XER(r1)
	mfdar	r7
	std	r7,_DAR(r1)
	mfdsisr	r8
	std	r8,_DSISR(r1)

	/* Temporary workaround to clear CR until RTAS can be modified to
	 * ignore all bits.
	 */
	li	r0,0
	mtcr	r0

	mfmsr	r6

	/* Unfortunately, the stack pointer and the MSR are also clobbered,
	 * so they are saved in the PACA which allows us to restore
	 * our original state after RTAS returns.
	 */
	std	r1,PACAR1(r13)
	std	r6,PACASAVEDMSR(r13)

	/* Setup our real return addr */
	LOAD_REG_ADDR(r4,rtas_return_loc)
	clrldi	r4,r4,2			/* convert to realmode address */
	mtlr	r4

__enter_rtas:
	LOAD_REG_ADDR(r4, rtas)
	ld	r5,RTASENTRY(r4)	/* get the rtas->entry value */
	ld	r4,RTASBASE(r4)		/* get the rtas->base value */

	/*
	 * RTAS runs in 32-bit big endian real mode, but leave MSR[RI] on as we
	 * may hit NMI (SRESET or MCE) while in RTAS. RTAS should disable RI in
	 * its critical regions (as specified in PAPR+ section 7.2.1). MSR[S]
	 * is not impacted by RFI_TO_KERNEL (only urfid can unset it). So if
	 * MSR[S] is set, it will remain when entering RTAS.
	 * If we're in HV mode, RTAS must also run in HV mode, so extract MSR_HV
	 * from the saved MSR value and insert into the value RTAS will use.
	 */
	extrdi	r0, r6, 1, 63 - MSR_HV_LG
	LOAD_REG_IMMEDIATE(r6, MSR_ME | MSR_RI)
	insrdi	r6, r0, 1, 63 - MSR_HV_LG

	li      r0,0
	mtmsrd  r0,1                    /* disable RI before using SRR0/1 */
	
	mtspr	SPRN_SRR0,r5
	mtspr	SPRN_SRR1,r6
	RFI_TO_KERNEL
	b	.	/* prevent speculative execution */
rtas_return_loc:
	FIXUP_ENDIAN

	/* Set SF before anything. */
	LOAD_REG_IMMEDIATE(r6, MSR_KERNEL & ~(MSR_IR|MSR_DR))
	mtmsrd	r6

	/* relocation is off at this point */
	GET_PACA(r13)

	bcl	20,31,$+4
0:	mflr	r3
	ld	r3,(1f-0b)(r3)		/* get &rtas_restore_regs */

	ld	r1,PACAR1(r13)		/* Restore our SP */
	ld	r4,PACASAVEDMSR(r13)	/* Restore our MSR */

	mtspr	SPRN_SRR0,r3
	mtspr	SPRN_SRR1,r4
	RFI_TO_KERNEL
	b	.	/* prevent speculative execution */
_ASM_NOKPROBE_SYMBOL(enter_rtas)
_ASM_NOKPROBE_SYMBOL(__enter_rtas)
_ASM_NOKPROBE_SYMBOL(rtas_return_loc)

	.align	3
1:	.8byte	rtas_restore_regs

rtas_restore_regs:
	/* relocation is on at this point */
	REST_GPR(2, r1)			/* Restore the TOC */
	REST_NVGPRS(r1)			/* Restore the non-volatiles */

	ld	r4,_CCR(r1)
	mtcr	r4
	ld	r5,_CTR(r1)
	mtctr	r5
	ld	r6,_XER(r1)
	mtspr	SPRN_XER,r6
	ld	r7,_DAR(r1)
	mtdar	r7
	ld	r8,_DSISR(r1)
	mtdsisr	r8

	addi	r1,r1,SWITCH_FRAME_SIZE	/* Unstack our frame */
	ld	r0,16(r1)		/* get return address */

	mtlr	r0
	blr				/* return to caller */

#endif /* CONFIG_PPC32 */
