// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2019 Western Digital Corporation or its affiliates. * * Authors: * Anup Patel <anup.patel@wdc.com> */ #include <linux/kvm_host.h> #include <asm/csr.h> #include <asm/insn-def.h> static int gstage_page_fault(struct kvm_vcpu *vcpu, struct kvm_run *run, struct kvm_cpu_trap *trap) { struct kvm_memory_slot *memslot; unsigned long hva, fault_addr; bool writable; gfn_t gfn; int ret; fault_addr = (trap->htval << 2) | (trap->stval & 0x3); gfn = fault_addr >> PAGE_SHIFT; memslot = gfn_to_memslot(vcpu->kvm, gfn); hva = gfn_to_hva_memslot_prot(memslot, gfn, &writable); if (kvm_is_error_hva(hva) || (trap->scause == EXC_STORE_GUEST_PAGE_FAULT && !writable)) { switch (trap->scause) { case EXC_LOAD_GUEST_PAGE_FAULT: return kvm_riscv_vcpu_mmio_load(vcpu, run, fault_addr, trap->htinst); case EXC_STORE_GUEST_PAGE_FAULT: return kvm_riscv_vcpu_mmio_store(vcpu, run, fault_addr, trap->htinst); default: return -EOPNOTSUPP; }; } ret = kvm_riscv_gstage_map(vcpu, memslot, fault_addr, hva, (trap->scause == EXC_STORE_GUEST_PAGE_FAULT) ? true : false); if (ret < 0) return ret; return 1; } /** * kvm_riscv_vcpu_unpriv_read -- Read machine word from Guest memory * * @vcpu: The VCPU pointer * @read_insn: Flag representing whether we are reading instruction * @guest_addr: Guest address to read * @trap: Output pointer to trap details */ unsigned long kvm_riscv_vcpu_unpriv_read(struct kvm_vcpu *vcpu, bool read_insn, unsigned long guest_addr, struct kvm_cpu_trap *trap) { register unsigned long taddr asm("a0") = (unsigned long)trap; register unsigned long ttmp asm("a1"); unsigned long flags, val, tmp, old_stvec, old_hstatus; local_irq_save(flags); old_hstatus = csr_swap(CSR_HSTATUS, vcpu->arch.guest_context.hstatus); old_stvec = csr_swap(CSR_STVEC, (ulong)&__kvm_riscv_unpriv_trap); if (read_insn) { /* * HLVX.HU instruction * 0110010 00011 rs1 100 rd 1110011 */ asm volatile ("\n" ".option push\n" ".option norvc\n" "add %[ttmp], %[taddr], 0\n" HLVX_HU(%[val], %[addr]) "andi %[tmp], %[val], 3\n" "addi %[tmp], %[tmp], -3\n" "bne %[tmp], zero, 2f\n" "addi %[addr], %[addr], 2\n" HLVX_HU(%[tmp], %[addr]) "sll %[tmp], %[tmp], 16\n" "add %[val], %[val], %[tmp]\n" "2:\n" ".option pop" : [val] "=&r" (val), [tmp] "=&r" (tmp), [taddr] "+&r" (taddr), [ttmp] "+&r" (ttmp), [addr] "+&r" (guest_addr) : : "memory"); if (trap->scause == EXC_LOAD_PAGE_FAULT) trap->scause = EXC_INST_PAGE_FAULT; } else { /* * HLV.D instruction * 0110110 00000 rs1 100 rd 1110011 * * HLV.W instruction * 0110100 00000 rs1 100 rd 1110011 */ asm volatile ("\n" ".option push\n" ".option norvc\n" "add %[ttmp], %[taddr], 0\n" #ifdef CONFIG_64BIT HLV_D(%[val], %[addr]) #else HLV_W(%[val], %[addr]) #endif ".option pop" : [val] "=&r" (val), [taddr] "+&r" (taddr), [ttmp] "+&r" (ttmp) : [addr] "r" (guest_addr) : "memory"); } csr_write(CSR_STVEC, old_stvec); csr_write(CSR_HSTATUS, old_hstatus); local_irq_restore(flags); return val; } /** * kvm_riscv_vcpu_trap_redirect -- Redirect trap to Guest * * @vcpu: The VCPU pointer * @trap: Trap details */ void kvm_riscv_vcpu_trap_redirect(struct kvm_vcpu *vcpu, struct kvm_cpu_trap *trap) { unsigned long vsstatus = csr_read(CSR_VSSTATUS); /* Change Guest SSTATUS.SPP bit */ vsstatus &= ~SR_SPP; if (vcpu->arch.guest_context.sstatus & SR_SPP) vsstatus |= SR_SPP; /* Change Guest SSTATUS.SPIE bit */ vsstatus &= ~SR_SPIE; if (vsstatus & SR_SIE) vsstatus |= SR_SPIE; /* Clear Guest SSTATUS.SIE bit */ vsstatus &= ~SR_SIE; /* Update Guest SSTATUS */ csr_write(CSR_VSSTATUS, vsstatus); /* Update Guest SCAUSE, STVAL, and SEPC */ csr_write(CSR_VSCAUSE, trap->scause); csr_write(CSR_VSTVAL, trap->stval); csr_write(CSR_VSEPC, trap->sepc); /* Set Guest PC to Guest exception vector */ vcpu->arch.guest_context.sepc = csr_read(CSR_VSTVEC); /* Set Guest privilege mode to supervisor */ vcpu->arch.guest_context.sstatus |= SR_SPP; } /* * Return > 0 to return to guest, < 0 on error, 0 (and set exit_reason) on * proper exit to userspace. */ int kvm_riscv_vcpu_exit(struct kvm_vcpu *vcpu, struct kvm_run *run, struct kvm_cpu_trap *trap) { int ret; /* If we got host interrupt then do nothing */ if (trap->scause & CAUSE_IRQ_FLAG) return 1; /* Handle guest traps */ ret = -EFAULT; run->exit_reason = KVM_EXIT_UNKNOWN; switch (trap->scause) { case EXC_INST_ILLEGAL: case EXC_LOAD_MISALIGNED: case EXC_STORE_MISALIGNED: if (vcpu->arch.guest_context.hstatus & HSTATUS_SPV) { kvm_riscv_vcpu_trap_redirect(vcpu, trap); ret = 1; } break; case EXC_VIRTUAL_INST_FAULT: if (vcpu->arch.guest_context.hstatus & HSTATUS_SPV) ret = kvm_riscv_vcpu_virtual_insn(vcpu, run, trap); break; case EXC_INST_GUEST_PAGE_FAULT: case EXC_LOAD_GUEST_PAGE_FAULT: case EXC_STORE_GUEST_PAGE_FAULT: if (vcpu->arch.guest_context.hstatus & HSTATUS_SPV) ret = gstage_page_fault(vcpu, run, trap); break; case EXC_SUPERVISOR_SYSCALL: if (vcpu->arch.guest_context.hstatus & HSTATUS_SPV) ret = kvm_riscv_vcpu_sbi_ecall(vcpu, run); break; default: break; } /* Print details in-case of error */ if (ret < 0) { kvm_err("VCPU exit error %d\n", ret); kvm_err("SEPC=0x%lx SSTATUS=0x%lx HSTATUS=0x%lx\n", vcpu->arch.guest_context.sepc, vcpu->arch.guest_context.sstatus, vcpu->arch.guest_context.hstatus); kvm_err("SCAUSE=0x%lx STVAL=0x%lx HTVAL=0x%lx HTINST=0x%lx\n", trap->scause, trap->stval, trap->htval, trap->htinst); } return ret; }