/* SPDX-License-Identifier: GPL-2.0-only */
/*
 *
 * Copyright 2011 Paul Mackerras, IBM Corp. <paulus@au1.ibm.com>
 *
 * Derived from book3s_interrupts.S, which is:
 * Copyright SUSE Linux Products GmbH 2009
 *
 * Authors: Alexander Graf <agraf@suse.de>
 */

#include <asm/ppc_asm.h>
#include <asm/kvm_asm.h>
#include <asm/reg.h>
#include <asm/page.h>
#include <asm/asm-offsets.h>
#include <asm/exception-64s.h>
#include <asm/ppc-opcode.h>
#include <asm/asm-compat.h>
#include <asm/feature-fixups.h>

/*****************************************************************************
 *                                                                           *
 *     Guest entry / exit code that is in kernel module memory (vmalloc)     *
 *                                                                           *
 ****************************************************************************/

/* Registers:
 *  none
 */
_GLOBAL(__kvmppc_vcore_entry)

	/* Write correct stack frame */
	mflr	r0
	std	r0,PPC_LR_STKOFF(r1)

	/* Save host state to the stack */
	stdu	r1, -SWITCH_FRAME_SIZE(r1)

	/* Save non-volatile registers (r14 - r31) and CR */
	SAVE_NVGPRS(r1)
	mfcr	r3
	std	r3, _CCR(r1)

	/* Save host DSCR */
	mfspr	r3, SPRN_DSCR
	std	r3, HSTATE_DSCR(r13)

BEGIN_FTR_SECTION
	/* Save host DABR */
	mfspr	r3, SPRN_DABR
	std	r3, HSTATE_DABR(r13)
END_FTR_SECTION_IFCLR(CPU_FTR_ARCH_207S)

	/* Save host PMU registers */
	bl	kvmhv_save_host_pmu

	/*
	 * Put whatever is in the decrementer into the
	 * hypervisor decrementer.
	 * Because of a hardware deviation in P8 and P9,
	 * we need to set LPCR[HDICE] before writing HDEC.
	 */
	ld	r5, HSTATE_KVM_VCORE(r13)
	ld	r6, VCORE_KVM(r5)
	ld	r9, KVM_HOST_LPCR(r6)
	ori	r8, r9, LPCR_HDICE
	mtspr	SPRN_LPCR, r8
	isync
	andis.	r0, r9, LPCR_LD@h
	mfspr	r8,SPRN_DEC
	mftb	r7
BEGIN_FTR_SECTION
	/* On POWER9, don't sign-extend if host LPCR[LD] bit is set */
	bne	32f
END_FTR_SECTION_IFSET(CPU_FTR_ARCH_300)
	extsw	r8,r8
32:	mtspr	SPRN_HDEC,r8
	add	r8,r8,r7
	std	r8,HSTATE_DECEXP(r13)

	/* Jump to partition switch code */
	bl	kvmppc_hv_entry_trampoline
	nop

/*
 * We return here in virtual mode after the guest exits
 * with something that we can't handle in real mode.
 * Interrupts are still hard-disabled.
 */

	/*
	 * Register usage at this point:
	 *
	 * R1       = host R1
	 * R2       = host R2
	 * R3       = trap number on this thread
	 * R12      = exit handler id
	 * R13      = PACA
	 */

	/* Restore non-volatile host registers (r14 - r31) and CR */
	REST_NVGPRS(r1)
	ld	r4, _CCR(r1)
	mtcr	r4

	addi    r1, r1, SWITCH_FRAME_SIZE
	ld	r0, PPC_LR_STKOFF(r1)
	mtlr	r0
	blr

_GLOBAL(kvmhv_save_host_pmu)
BEGIN_FTR_SECTION
	/* Work around P8 PMAE bug */
	li	r3, -1
	clrrdi	r3, r3, 10
	mfspr	r8, SPRN_MMCR2
	mtspr	SPRN_MMCR2, r3		/* freeze all counters using MMCR2 */
	isync
END_FTR_SECTION_IFSET(CPU_FTR_ARCH_207S)
	li	r3, 1
	sldi	r3, r3, 31		/* MMCR0_FC (freeze counters) bit */
	mfspr	r7, SPRN_MMCR0		/* save MMCR0 */
	mtspr	SPRN_MMCR0, r3		/* freeze all counters, disable interrupts */
	mfspr	r6, SPRN_MMCRA
	/* Clear MMCRA in order to disable SDAR updates */
	li	r5, 0
	mtspr	SPRN_MMCRA, r5
	isync
	lbz	r5, PACA_PMCINUSE(r13)	/* is the host using the PMU? */
	cmpwi	r5, 0
	beq	31f			/* skip if not */
	mfspr	r5, SPRN_MMCR1
	mfspr	r9, SPRN_SIAR
	mfspr	r10, SPRN_SDAR
	std	r7, HSTATE_MMCR0(r13)
	std	r5, HSTATE_MMCR1(r13)
	std	r6, HSTATE_MMCRA(r13)
	std	r9, HSTATE_SIAR(r13)
	std	r10, HSTATE_SDAR(r13)
BEGIN_FTR_SECTION
	mfspr	r9, SPRN_SIER
	std	r8, HSTATE_MMCR2(r13)
	std	r9, HSTATE_SIER(r13)
END_FTR_SECTION_IFSET(CPU_FTR_ARCH_207S)
BEGIN_FTR_SECTION
	mfspr	r5, SPRN_MMCR3
	mfspr	r6, SPRN_SIER2
	mfspr	r7, SPRN_SIER3
	std	r5, HSTATE_MMCR3(r13)
	std	r6, HSTATE_SIER2(r13)
	std	r7, HSTATE_SIER3(r13)
END_FTR_SECTION_IFSET(CPU_FTR_ARCH_31)
	mfspr	r3, SPRN_PMC1
	mfspr	r5, SPRN_PMC2
	mfspr	r6, SPRN_PMC3
	mfspr	r7, SPRN_PMC4
	mfspr	r8, SPRN_PMC5
	mfspr	r9, SPRN_PMC6
	stw	r3, HSTATE_PMC1(r13)
	stw	r5, HSTATE_PMC2(r13)
	stw	r6, HSTATE_PMC3(r13)
	stw	r7, HSTATE_PMC4(r13)
	stw	r8, HSTATE_PMC5(r13)
	stw	r9, HSTATE_PMC6(r13)
31:	blr
