| // 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); |
| } |
| |
| /* |
| * 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_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; |
| } |