/*
 * Test Special Purpose Registers
 *
 * Copyright 2017  Thomas Huth, Red Hat Inc.
 *
 * This work is licensed under the terms of the GNU LGPL, version 2.
 *
 * The basic idea of this test is to check whether the contents of the Special
 * Purpose Registers (SPRs) are preserved correctly during migration. So we
 * fill in the SPRs with a well-known value, read the values back (since not
 * all bits might be retained in the SPRs), then wait for migration to complete
 * (if the '-w' option has been specified) so that the user has a chance to
 * migrate the VM. Alternatively, the test can also simply sleep a little bit
 * with the H_CEDE hypercall, in the hope that we'll get scheduled to another
 * host CPU and thus register contents might have changed, too (in case of
 * bugs). Finally, we read back the values from the SPRs and compare them with
 * the values before the migration. Mismatches are reported as test failures.
 * Note that we do not test all SPRs since some of the registers change their
 * content automatically, and some are only accessible with hypervisor privi-
 * leges or have bad side effects, so we have to omit those registers.
 */
#include <libcflat.h>
#include <util.h>
#include <alloc.h>
#include <asm/handlers.h>
#include <asm/hcall.h>
#include <asm/processor.h>
#include <asm/barrier.h>

#define mfspr(nr) ({ \
	uint64_t ret; \
	asm volatile("mfspr %0,%1" : "=r"(ret) : "i"(nr)); \
	ret; \
})

#define mtspr(nr, val) \
	asm volatile("mtspr %0,%1" : : "i"(nr), "r"(val))

uint64_t before[1024], after[1024];

/* Common SPRs for all PowerPC CPUs */
static void set_sprs_common(uint64_t val)
{
	mtspr(9, val);		/* CTR */
	// mtspr(273, val);	/* SPRG1 */  /* Used by our exception handler */
	mtspr(274, val);	/* SPRG2 */
	mtspr(275, val);	/* SPRG3 */
}

/* SPRs from PowerPC Operating Environment Architecture, Book III, Vers. 2.01 */
static void set_sprs_book3s_201(uint64_t val)
{
	mtspr(18, val);		/* DSISR */
	mtspr(19, val);		/* DAR */
	mtspr(152, val);	/* CTRL */
	mtspr(256, val);	/* VRSAVE */
	mtspr(786, val);	/* MMCRA */
	mtspr(795, val);	/* MMCR0 */
	mtspr(798, val);	/* MMCR1 */
}

/* SPRs from PowerISA 2.07 Book III-S */
static void set_sprs_book3s_207(uint64_t val)
{
	mtspr(3, val);		/* DSCR */
	mtspr(13, val);		/* AMR */
	mtspr(17, val);		/* DSCR */
	mtspr(18, val);		/* DSISR */
	mtspr(19, val);		/* DAR */
	mtspr(29, val);		/* AMR */
	mtspr(61, val);		/* IAMR */
	// mtspr(152, val);	/* CTRL */  /* TODO: Needs a fix in KVM */
	mtspr(153, val);	/* FSCR */
	mtspr(157, val);	/* UAMOR */
	mtspr(159, val);	/* PSPB */
	mtspr(256, val);	/* VRSAVE */
	// mtspr(272, val);	/* SPRG0 */ /* Used by our exception handler */
	mtspr(769, val);	/* MMCR2 */
	mtspr(770, val);	/* MMCRA */
	mtspr(771, val);	/* PMC1 */
	mtspr(772, val);	/* PMC2 */
	mtspr(773, val);	/* PMC3 */
	mtspr(774, val);	/* PMC4 */
	mtspr(775, val);	/* PMC5 */
	mtspr(776, val);	/* PMC6 */
	mtspr(779, (val & 0xfffffffffbab3fffULL) | 0xfa0b2070);	/* MMCR0 */
	mtspr(784, val);	/* SIER */
	mtspr(785, val);	/* MMCR2 */
	mtspr(786, val);	/* MMCRA */
	mtspr(787, val);	/* PMC1 */
	mtspr(788, val);	/* PMC2 */
	mtspr(789, val);	/* PMC3 */
	mtspr(790, val);	/* PMC4 */
	mtspr(791, val);	/* PMC5 */
	mtspr(792, val);	/* PMC6 */
	mtspr(795, (val & 0xfffffffffbab3fffULL) | 0xfa0b2070);	/* MMCR0 */
	mtspr(796, val);	/* SIAR */
	mtspr(797, val);	/* SDAR */
	mtspr(798, val);	/* MMCR1 */
	mtspr(800, val);	/* BESCRS */
	mtspr(801, val);	/* BESCCRSU */
	mtspr(802, val);	/* BESCRR */
	mtspr(803, val);	/* BESCRRU */
	mtspr(804, val);	/* EBBHR */
	mtspr(805, val);	/* EBBRR */
	mtspr(806, val);	/* BESCR */
	mtspr(815, val);	/* TAR */
}

/* SPRs from PowerISA 3.00 Book III */
static void set_sprs_book3s_300(uint64_t val)
{
	set_sprs_book3s_207(val);
	mtspr(48, val);		/* PIDR */
	mtspr(144, val);	/* TIDR */
	mtspr(823, val);	/* PSSCR */
}

static void set_sprs(uint64_t val)
{
	uint32_t pvr = mfspr(287);	/* Processor Version Register */

	set_sprs_common(val);

	switch (pvr >> 16) {
	case 0x39:			/* PPC970 */
	case 0x3C:			/* PPC970FX */
	case 0x44:			/* PPC970MP */
		set_sprs_book3s_201(val);
		break;
	case 0x4b:			/* POWER8E */
	case 0x4c:			/* POWER8NVL */
	case 0x4d:			/* POWER8 */
		set_sprs_book3s_207(val);
		break;
	case 0x4e:			/* POWER9 */
		set_sprs_book3s_300(val);
		break;
	default:
		puts("Warning: Unknown processor version!\n");
	}
}

static void get_sprs_common(uint64_t *v)
{
	v[9] = mfspr(9);	/* CTR */
	// v[273] = mfspr(273);	/* SPRG1 */ /* Used by our exception handler */
	v[274] = mfspr(274);	/* SPRG2 */
	v[275] = mfspr(275);	/* SPRG3 */
}

static void get_sprs_book3s_201(uint64_t *v)
{
	v[18] = mfspr(18);	/* DSISR */
	v[19] = mfspr(19);	/* DAR */
	v[136] = mfspr(136);	/* CTRL */
	v[256] = mfspr(256);	/* VRSAVE */
	v[786] = mfspr(786);	/* MMCRA */
	v[795] = mfspr(795);	/* MMCR0 */
	v[798] = mfspr(798);	/* MMCR1 */
}

static void get_sprs_book3s_207(uint64_t *v)
{
	v[3] = mfspr(3);	/* DSCR */
	v[13] = mfspr(13);	/* AMR */
	v[17] = mfspr(17);	/* DSCR */
	v[18] = mfspr(18);	/* DSISR */
	v[19] = mfspr(19);	/* DAR */
	v[29] = mfspr(29);	/* AMR */
	v[61] = mfspr(61);	/* IAMR */
	// v[136] = mfspr(136);	/* CTRL */  /* TODO: Needs a fix in KVM */
	v[153] = mfspr(153);	/* FSCR */
	v[157] = mfspr(157);	/* UAMOR */
	v[159] = mfspr(159);	/* PSPB */
	v[256] = mfspr(256);	/* VRSAVE */
	v[259] = mfspr(259);	/* SPRG3 (read only) */
	// v[272] = mfspr(272);	/* SPRG0 */  /* Used by our exception handler */
	v[769] = mfspr(769);	/* MMCR2 */
	v[770] = mfspr(770);	/* MMCRA */
	v[771] = mfspr(771);	/* PMC1 */
	v[772] = mfspr(772);	/* PMC2 */
	v[773] = mfspr(773);	/* PMC3 */
	v[774] = mfspr(774);	/* PMC4 */
	v[775] = mfspr(775);	/* PMC5 */
	v[776] = mfspr(776);	/* PMC6 */
	v[779] = mfspr(779);	/* MMCR0 */
	v[780] = mfspr(780);	/* SIAR (read only) */
	v[781] = mfspr(781);	/* SDAR (read only) */
	v[782] = mfspr(782);	/* MMCR1 (read only) */
	v[784] = mfspr(784);	/* SIER */
	v[785] = mfspr(785);	/* MMCR2 */
	v[786] = mfspr(786);	/* MMCRA */
	v[787] = mfspr(787);	/* PMC1 */
	v[788] = mfspr(788);	/* PMC2 */
	v[789] = mfspr(789);	/* PMC3 */
	v[790] = mfspr(790);	/* PMC4 */
	v[791] = mfspr(791);	/* PMC5 */
	v[792] = mfspr(792);	/* PMC6 */
	v[795] = mfspr(795);	/* MMCR0 */
	v[796] = mfspr(796);	/* SIAR */
	v[797] = mfspr(797);	/* SDAR */
	v[798] = mfspr(798);	/* MMCR1 */
	v[800] = mfspr(800);	/* BESCRS */
	v[801] = mfspr(801);	/* BESCCRSU */
	v[802] = mfspr(802);	/* BESCRR */
	v[803] = mfspr(803);	/* BESCRRU */
	v[804] = mfspr(804);	/* EBBHR */
	v[805] = mfspr(805);	/* EBBRR */
	v[806] = mfspr(806);	/* BESCR */
	v[815] = mfspr(815);	/* TAR */
}

static void get_sprs_book3s_300(uint64_t *v)
{
	get_sprs_book3s_207(v);
	v[48] = mfspr(48);	/* PIDR */
	v[144] = mfspr(144);	/* TIDR */
	v[823] = mfspr(823);	/* PSSCR */
}

static void get_sprs(uint64_t *v)
{
	uint32_t pvr = mfspr(287);	/* Processor Version Register */

	get_sprs_common(v);

	switch (pvr >> 16) {
	case 0x39:			/* PPC970 */
	case 0x3C:			/* PPC970FX */
	case 0x44:			/* PPC970MP */
		get_sprs_book3s_201(v);
		break;
	case 0x4b:			/* POWER8E */
	case 0x4c:			/* POWER8NVL */
	case 0x4d:			/* POWER8 */
		get_sprs_book3s_207(v);
		break;
	case 0x4e:			/* POWER9 */
		get_sprs_book3s_300(v);
		break;
	}
}

int main(int argc, char **argv)
{
	int i;
	bool pause = false;
	uint64_t pat = 0xcafefacec0debabeULL;
	const uint64_t patterns[] = {
		0xcafefacec0debabeULL, ~0xcafefacec0debabeULL,
		0xAAAA5555AAAA5555ULL, 0x5555AAAA5555AAAAULL,
		0x1234567890ABCDEFULL, 0xFEDCBA0987654321ULL,
		-1ULL,
	};

	for (i = 1; i < argc; i++) {
		if (!strcmp(argv[i], "-w")) {
			pause = true;
		} else if (!strcmp(argv[i], "-p")) {
			i += 1;
			if (i >= argc || *argv[i] < '0'
			    || *argv[i] >= '0' + ARRAY_SIZE(patterns))
				report_abort("Error: bad value for -p");
			pat ^= patterns[*argv[i] - '0'];
		} else if (!strcmp(argv[i], "-t")) {
			/* Randomize with timebase register */
			asm volatile("mftb %0" : "=r"(i));
			pat ^= i;
			asm volatile("mftb %0" : "=r"(i));
			pat ^= ~(uint64_t)i << 32;
		} else {
			report_abort("Warning: Unsupported argument: %s",
			             argv[i]);
		}
	}

	printf("Settings SPRs to %#lx...\n", pat);
	set_sprs(pat);

	memset(before, 0, sizeof(before));
	memset(after, 0, sizeof(after));

	get_sprs(before);

	if (pause) {
		puts("Now migrate the VM, then press a key to continue...\n");
		(void) getchar();
	} else {
		puts("Sleeping...\n");
		handle_exception(0x900, &dec_except_handler, NULL);
		asm volatile ("mtdec %0" : : "r" (0x3FFFFFFF));
		hcall(H_CEDE);
	}

	get_sprs(after);

	puts("Checking SPRs...\n");
	for (i = 0; i < 1024; i++) {
		if (before[i] != 0 || after[i] != 0)
			report("SPR %d:\t%#018lx <==> %#018lx",
				before[i] == after[i], i, before[i], after[i]);
	}

	return report_summary();
}
