162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2019 Western Digital Corporation or its affiliates.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors:
662306a36Sopenharmony_ci *     Anup Patel <anup.patel@wdc.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/kvm_host.h>
1062306a36Sopenharmony_ci#include <asm/csr.h>
1162306a36Sopenharmony_ci#include <asm/insn-def.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_cistatic int gstage_page_fault(struct kvm_vcpu *vcpu, struct kvm_run *run,
1462306a36Sopenharmony_ci			     struct kvm_cpu_trap *trap)
1562306a36Sopenharmony_ci{
1662306a36Sopenharmony_ci	struct kvm_memory_slot *memslot;
1762306a36Sopenharmony_ci	unsigned long hva, fault_addr;
1862306a36Sopenharmony_ci	bool writable;
1962306a36Sopenharmony_ci	gfn_t gfn;
2062306a36Sopenharmony_ci	int ret;
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci	fault_addr = (trap->htval << 2) | (trap->stval & 0x3);
2362306a36Sopenharmony_ci	gfn = fault_addr >> PAGE_SHIFT;
2462306a36Sopenharmony_ci	memslot = gfn_to_memslot(vcpu->kvm, gfn);
2562306a36Sopenharmony_ci	hva = gfn_to_hva_memslot_prot(memslot, gfn, &writable);
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	if (kvm_is_error_hva(hva) ||
2862306a36Sopenharmony_ci	    (trap->scause == EXC_STORE_GUEST_PAGE_FAULT && !writable)) {
2962306a36Sopenharmony_ci		switch (trap->scause) {
3062306a36Sopenharmony_ci		case EXC_LOAD_GUEST_PAGE_FAULT:
3162306a36Sopenharmony_ci			return kvm_riscv_vcpu_mmio_load(vcpu, run,
3262306a36Sopenharmony_ci							fault_addr,
3362306a36Sopenharmony_ci							trap->htinst);
3462306a36Sopenharmony_ci		case EXC_STORE_GUEST_PAGE_FAULT:
3562306a36Sopenharmony_ci			return kvm_riscv_vcpu_mmio_store(vcpu, run,
3662306a36Sopenharmony_ci							 fault_addr,
3762306a36Sopenharmony_ci							 trap->htinst);
3862306a36Sopenharmony_ci		default:
3962306a36Sopenharmony_ci			return -EOPNOTSUPP;
4062306a36Sopenharmony_ci		};
4162306a36Sopenharmony_ci	}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	ret = kvm_riscv_gstage_map(vcpu, memslot, fault_addr, hva,
4462306a36Sopenharmony_ci		(trap->scause == EXC_STORE_GUEST_PAGE_FAULT) ? true : false);
4562306a36Sopenharmony_ci	if (ret < 0)
4662306a36Sopenharmony_ci		return ret;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	return 1;
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci/**
5262306a36Sopenharmony_ci * kvm_riscv_vcpu_unpriv_read -- Read machine word from Guest memory
5362306a36Sopenharmony_ci *
5462306a36Sopenharmony_ci * @vcpu: The VCPU pointer
5562306a36Sopenharmony_ci * @read_insn: Flag representing whether we are reading instruction
5662306a36Sopenharmony_ci * @guest_addr: Guest address to read
5762306a36Sopenharmony_ci * @trap: Output pointer to trap details
5862306a36Sopenharmony_ci */
5962306a36Sopenharmony_ciunsigned long kvm_riscv_vcpu_unpriv_read(struct kvm_vcpu *vcpu,
6062306a36Sopenharmony_ci					 bool read_insn,
6162306a36Sopenharmony_ci					 unsigned long guest_addr,
6262306a36Sopenharmony_ci					 struct kvm_cpu_trap *trap)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	register unsigned long taddr asm("a0") = (unsigned long)trap;
6562306a36Sopenharmony_ci	register unsigned long ttmp asm("a1");
6662306a36Sopenharmony_ci	unsigned long flags, val, tmp, old_stvec, old_hstatus;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	local_irq_save(flags);
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	old_hstatus = csr_swap(CSR_HSTATUS, vcpu->arch.guest_context.hstatus);
7162306a36Sopenharmony_ci	old_stvec = csr_swap(CSR_STVEC, (ulong)&__kvm_riscv_unpriv_trap);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if (read_insn) {
7462306a36Sopenharmony_ci		/*
7562306a36Sopenharmony_ci		 * HLVX.HU instruction
7662306a36Sopenharmony_ci		 * 0110010 00011 rs1 100 rd 1110011
7762306a36Sopenharmony_ci		 */
7862306a36Sopenharmony_ci		asm volatile ("\n"
7962306a36Sopenharmony_ci			".option push\n"
8062306a36Sopenharmony_ci			".option norvc\n"
8162306a36Sopenharmony_ci			"add %[ttmp], %[taddr], 0\n"
8262306a36Sopenharmony_ci			HLVX_HU(%[val], %[addr])
8362306a36Sopenharmony_ci			"andi %[tmp], %[val], 3\n"
8462306a36Sopenharmony_ci			"addi %[tmp], %[tmp], -3\n"
8562306a36Sopenharmony_ci			"bne %[tmp], zero, 2f\n"
8662306a36Sopenharmony_ci			"addi %[addr], %[addr], 2\n"
8762306a36Sopenharmony_ci			HLVX_HU(%[tmp], %[addr])
8862306a36Sopenharmony_ci			"sll %[tmp], %[tmp], 16\n"
8962306a36Sopenharmony_ci			"add %[val], %[val], %[tmp]\n"
9062306a36Sopenharmony_ci			"2:\n"
9162306a36Sopenharmony_ci			".option pop"
9262306a36Sopenharmony_ci		: [val] "=&r" (val), [tmp] "=&r" (tmp),
9362306a36Sopenharmony_ci		  [taddr] "+&r" (taddr), [ttmp] "+&r" (ttmp),
9462306a36Sopenharmony_ci		  [addr] "+&r" (guest_addr) : : "memory");
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		if (trap->scause == EXC_LOAD_PAGE_FAULT)
9762306a36Sopenharmony_ci			trap->scause = EXC_INST_PAGE_FAULT;
9862306a36Sopenharmony_ci	} else {
9962306a36Sopenharmony_ci		/*
10062306a36Sopenharmony_ci		 * HLV.D instruction
10162306a36Sopenharmony_ci		 * 0110110 00000 rs1 100 rd 1110011
10262306a36Sopenharmony_ci		 *
10362306a36Sopenharmony_ci		 * HLV.W instruction
10462306a36Sopenharmony_ci		 * 0110100 00000 rs1 100 rd 1110011
10562306a36Sopenharmony_ci		 */
10662306a36Sopenharmony_ci		asm volatile ("\n"
10762306a36Sopenharmony_ci			".option push\n"
10862306a36Sopenharmony_ci			".option norvc\n"
10962306a36Sopenharmony_ci			"add %[ttmp], %[taddr], 0\n"
11062306a36Sopenharmony_ci#ifdef CONFIG_64BIT
11162306a36Sopenharmony_ci			HLV_D(%[val], %[addr])
11262306a36Sopenharmony_ci#else
11362306a36Sopenharmony_ci			HLV_W(%[val], %[addr])
11462306a36Sopenharmony_ci#endif
11562306a36Sopenharmony_ci			".option pop"
11662306a36Sopenharmony_ci		: [val] "=&r" (val),
11762306a36Sopenharmony_ci		  [taddr] "+&r" (taddr), [ttmp] "+&r" (ttmp)
11862306a36Sopenharmony_ci		: [addr] "r" (guest_addr) : "memory");
11962306a36Sopenharmony_ci	}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	csr_write(CSR_STVEC, old_stvec);
12262306a36Sopenharmony_ci	csr_write(CSR_HSTATUS, old_hstatus);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	local_irq_restore(flags);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	return val;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci/**
13062306a36Sopenharmony_ci * kvm_riscv_vcpu_trap_redirect -- Redirect trap to Guest
13162306a36Sopenharmony_ci *
13262306a36Sopenharmony_ci * @vcpu: The VCPU pointer
13362306a36Sopenharmony_ci * @trap: Trap details
13462306a36Sopenharmony_ci */
13562306a36Sopenharmony_civoid kvm_riscv_vcpu_trap_redirect(struct kvm_vcpu *vcpu,
13662306a36Sopenharmony_ci				  struct kvm_cpu_trap *trap)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	unsigned long vsstatus = csr_read(CSR_VSSTATUS);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	/* Change Guest SSTATUS.SPP bit */
14162306a36Sopenharmony_ci	vsstatus &= ~SR_SPP;
14262306a36Sopenharmony_ci	if (vcpu->arch.guest_context.sstatus & SR_SPP)
14362306a36Sopenharmony_ci		vsstatus |= SR_SPP;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	/* Change Guest SSTATUS.SPIE bit */
14662306a36Sopenharmony_ci	vsstatus &= ~SR_SPIE;
14762306a36Sopenharmony_ci	if (vsstatus & SR_SIE)
14862306a36Sopenharmony_ci		vsstatus |= SR_SPIE;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	/* Clear Guest SSTATUS.SIE bit */
15162306a36Sopenharmony_ci	vsstatus &= ~SR_SIE;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	/* Update Guest SSTATUS */
15462306a36Sopenharmony_ci	csr_write(CSR_VSSTATUS, vsstatus);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	/* Update Guest SCAUSE, STVAL, and SEPC */
15762306a36Sopenharmony_ci	csr_write(CSR_VSCAUSE, trap->scause);
15862306a36Sopenharmony_ci	csr_write(CSR_VSTVAL, trap->stval);
15962306a36Sopenharmony_ci	csr_write(CSR_VSEPC, trap->sepc);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	/* Set Guest PC to Guest exception vector */
16262306a36Sopenharmony_ci	vcpu->arch.guest_context.sepc = csr_read(CSR_VSTVEC);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	/* Set Guest privilege mode to supervisor */
16562306a36Sopenharmony_ci	vcpu->arch.guest_context.sstatus |= SR_SPP;
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci/*
16962306a36Sopenharmony_ci * Return > 0 to return to guest, < 0 on error, 0 (and set exit_reason) on
17062306a36Sopenharmony_ci * proper exit to userspace.
17162306a36Sopenharmony_ci */
17262306a36Sopenharmony_ciint kvm_riscv_vcpu_exit(struct kvm_vcpu *vcpu, struct kvm_run *run,
17362306a36Sopenharmony_ci			struct kvm_cpu_trap *trap)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	int ret;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	/* If we got host interrupt then do nothing */
17862306a36Sopenharmony_ci	if (trap->scause & CAUSE_IRQ_FLAG)
17962306a36Sopenharmony_ci		return 1;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	/* Handle guest traps */
18262306a36Sopenharmony_ci	ret = -EFAULT;
18362306a36Sopenharmony_ci	run->exit_reason = KVM_EXIT_UNKNOWN;
18462306a36Sopenharmony_ci	switch (trap->scause) {
18562306a36Sopenharmony_ci	case EXC_INST_ILLEGAL:
18662306a36Sopenharmony_ci	case EXC_LOAD_MISALIGNED:
18762306a36Sopenharmony_ci	case EXC_STORE_MISALIGNED:
18862306a36Sopenharmony_ci		if (vcpu->arch.guest_context.hstatus & HSTATUS_SPV) {
18962306a36Sopenharmony_ci			kvm_riscv_vcpu_trap_redirect(vcpu, trap);
19062306a36Sopenharmony_ci			ret = 1;
19162306a36Sopenharmony_ci		}
19262306a36Sopenharmony_ci		break;
19362306a36Sopenharmony_ci	case EXC_VIRTUAL_INST_FAULT:
19462306a36Sopenharmony_ci		if (vcpu->arch.guest_context.hstatus & HSTATUS_SPV)
19562306a36Sopenharmony_ci			ret = kvm_riscv_vcpu_virtual_insn(vcpu, run, trap);
19662306a36Sopenharmony_ci		break;
19762306a36Sopenharmony_ci	case EXC_INST_GUEST_PAGE_FAULT:
19862306a36Sopenharmony_ci	case EXC_LOAD_GUEST_PAGE_FAULT:
19962306a36Sopenharmony_ci	case EXC_STORE_GUEST_PAGE_FAULT:
20062306a36Sopenharmony_ci		if (vcpu->arch.guest_context.hstatus & HSTATUS_SPV)
20162306a36Sopenharmony_ci			ret = gstage_page_fault(vcpu, run, trap);
20262306a36Sopenharmony_ci		break;
20362306a36Sopenharmony_ci	case EXC_SUPERVISOR_SYSCALL:
20462306a36Sopenharmony_ci		if (vcpu->arch.guest_context.hstatus & HSTATUS_SPV)
20562306a36Sopenharmony_ci			ret = kvm_riscv_vcpu_sbi_ecall(vcpu, run);
20662306a36Sopenharmony_ci		break;
20762306a36Sopenharmony_ci	default:
20862306a36Sopenharmony_ci		break;
20962306a36Sopenharmony_ci	}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	/* Print details in-case of error */
21262306a36Sopenharmony_ci	if (ret < 0) {
21362306a36Sopenharmony_ci		kvm_err("VCPU exit error %d\n", ret);
21462306a36Sopenharmony_ci		kvm_err("SEPC=0x%lx SSTATUS=0x%lx HSTATUS=0x%lx\n",
21562306a36Sopenharmony_ci			vcpu->arch.guest_context.sepc,
21662306a36Sopenharmony_ci			vcpu->arch.guest_context.sstatus,
21762306a36Sopenharmony_ci			vcpu->arch.guest_context.hstatus);
21862306a36Sopenharmony_ci		kvm_err("SCAUSE=0x%lx STVAL=0x%lx HTVAL=0x%lx HTINST=0x%lx\n",
21962306a36Sopenharmony_ci			trap->scause, trap->stval, trap->htval, trap->htinst);
22062306a36Sopenharmony_ci	}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	return ret;
22362306a36Sopenharmony_ci}
224