// SPDX-License-Identifier: GPL-2.0

#include <stdio.h>
#include <string.h>
#include "kvm_util.h"
#include "processor.h"
#include "loongarch/processor.h"

struct kvm_fpureg __aligned(64) vector = {{1, 2, 3, 4 }};

static void guest_code(void)
{
	unsigned long val;
	struct kvm_fpureg *fp = &vector;

	val = csr_read(LOONGARCH_CSR_EUEN);
	val |= CSR_EUEN_FPEN | CSR_EUEN_LSXEN | CSR_EUEN_LASXEN;
	csr_write(val, LOONGARCH_CSR_EUEN);

	__asm__ __volatile__("fld.d $f0, %0, 0\n" : : "r"(fp) : "$f0");
	GUEST_SYNC(0);

	__asm__ __volatile__("vld $vr0, %0, 0\n" : : "r"(fp) : "$vr0");
	GUEST_SYNC(1);

	__asm__ __volatile__("xvld $xr0, %0, 0\n" : : "r"(fp) : "$xr0");
	GUEST_SYNC(2);

	__asm__ __volatile__("fst.d $f0, %0, 0\n" : : "r"(fp) : "memory");
	GUEST_SYNC(3);

	__asm__ __volatile__("vst $vr0, %0, 0\n" : : "r"(fp) : "memory");
	GUEST_SYNC(4);

	__asm__ __volatile__("xvst $xr0, %0, 0\n" : : "r"(fp) : "memory");
	GUEST_SYNC(5);

	GUEST_DONE();
}

static void run_vcpu(struct kvm_vcpu *vcpu)
{
	int cont = 1;
	struct ucall uc;

	while (cont) {
		vcpu_run(vcpu);

		switch (get_ucall(vcpu, &uc)) {
		case UCALL_PRINTF:
			printf("%s", (const char *)uc.buffer);
			break;
		case UCALL_DONE:
			printf("FPU test PASSED\n");
			fallthrough;
		case UCALL_SYNC:
			cont = 0;
			break;
		case UCALL_ABORT:
			REPORT_GUEST_ASSERT(uc);
		default:
			TEST_ASSERT(false, "Unexpected exit: %s",
				exit_reason_str(vcpu->run->exit_reason));
		}
	}
}

int main(void)
{
	int ret;
	struct kvm_vm *vm;
	struct kvm_vcpu *vcpu;
	struct kvm_fpu fpu;
	struct kvm_fpureg *fp = &vector;

	vm = vm_create_with_one_vcpu(&vcpu, guest_code);

	ret = __kvm_has_device_attr(vm->fd, KVM_LOONGARCH_VM_FEAT_CTRL, KVM_LOONGARCH_VM_FEAT_LSX);
	__TEST_REQUIRE(!ret, "LSX not available, skipping test\n");

	ret = __kvm_has_device_attr(vm->fd, KVM_LOONGARCH_VM_FEAT_CTRL, KVM_LOONGARCH_VM_FEAT_LASX);
	__TEST_REQUIRE(!ret, "LASX not available, skipping test\n");

	run_vcpu(vcpu);
	vcpu_fpu_get(vcpu, &fpu);
	TEST_ASSERT(!memcmp(fpu.fpr, fp, 8), "Wanted 0x%llx from f0, got 0x%llx",
			fp->val64[0], fpu.fpr[0].val64[0]);

	run_vcpu(vcpu);
	vcpu_fpu_get(vcpu, &fpu);
	TEST_ASSERT(!memcmp(fpu.fpr, fp, 16), "Wanted 0x%llx %llx from vr0, got 0x%llx %llx",
			fp->val64[0], fp->val64[1],
			fpu.fpr[0].val64[0], fpu.fpr[0].val64[1]);

	run_vcpu(vcpu);
	vcpu_fpu_get(vcpu, &fpu);
	TEST_ASSERT(!memcmp(fpu.fpr, fp, 32),
			"Wanted 0x%llx %llx %llx %llx from xr0, got 0x%llx %llx %llx %llx",
			fp->val64[0], fp->val64[1], fp->val64[2], fp->val64[3],
			fpu.fpr[0].val64[0], fpu.fpr[0].val64[1], fpu.fpr[0].val64[2], fpu.fpr[0].val64[3]);

	fpu.fpr[0].val64[0] += random();
	vcpu_fpu_set(vcpu, &fpu);
	run_vcpu(vcpu);
	vcpu_fpu_get(vcpu, &fpu);
	sync_global_from_guest(vm, *fp);
	TEST_ASSERT(!memcmp(fpu.fpr, fp, 8), "Wanted 0x%llx from f0, got 0x%llx",
			fp->val64[0], fpu.fpr[0].val64[0]);

	fpu.fpr[0].val64[0] += random();
	fpu.fpr[0].val64[1] += random();
	vcpu_fpu_set(vcpu, &fpu);
	run_vcpu(vcpu);
	vcpu_fpu_get(vcpu, &fpu);
	sync_global_from_guest(vm, *fp);
	TEST_ASSERT(!memcmp(fpu.fpr, fp, 16), "Wanted 0x%llx %llx from vr0, got 0x%llx %llx",
			fp->val64[0], fp->val64[1],
			fpu.fpr[0].val64[0], fpu.fpr[0].val64[1]);

	fpu.fpr[0].val64[0] += random();
	fpu.fpr[0].val64[1] += random();
	fpu.fpr[0].val64[2] += random();
	fpu.fpr[0].val64[3] += random();
	vcpu_fpu_set(vcpu, &fpu);
	run_vcpu(vcpu);
	vcpu_fpu_get(vcpu, &fpu);
	sync_global_from_guest(vm, *fp);
	TEST_ASSERT(!memcmp(fpu.fpr, fp, 32),
			"Wanted 0x%llx %llx %llx %llx from xr0, got 0x%llx %llx %llx %llx",
			fp->val64[0], fp->val64[1], fp->val64[2], fp->val64[3],
			fpu.fpr[0].val64[0], fpu.fpr[0].val64[1], fpu.fpr[0].val64[2], fpu.fpr[0].val64[3]);

	run_vcpu(vcpu);
	kvm_vm_free(vm);

	return 0;
}