| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2020-2023 Loongson Technology Corporation Limited | |
| */ | |
| #include <linux/err.h> | |
| #include <linux/errno.h> | |
| #include <linux/kvm_host.h> | |
| #include <linux/module.h> | |
| #include <linux/preempt.h> | |
| #include <linux/vmalloc.h> | |
| #include <trace/events/kvm.h> | |
| #include <asm/fpu.h> | |
| #include <asm/inst.h> | |
| #include <asm/loongarch.h> | |
| #include <asm/mmzone.h> | |
| #include <asm/numa.h> | |
| #include <asm/time.h> | |
| #include <asm/tlb.h> | |
| #include <asm/kvm_csr.h> | |
| #include <asm/kvm_vcpu.h> | |
| #include "trace.h" | |
| static int kvm_emu_cpucfg(struct kvm_vcpu *vcpu, larch_inst inst) | |
| { | |
| int rd, rj; | |
| unsigned int index, ret; | |
| if (inst.reg2_format.opcode != cpucfg_op) | |
| return EMULATE_FAIL; | |
| rd = inst.reg2_format.rd; | |
| rj = inst.reg2_format.rj; | |
| ++vcpu->stat.cpucfg_exits; | |
| index = vcpu->arch.gprs[rj]; | |
| /* | |
| * By LoongArch Reference Manual 2.2.10.5 | |
| * Return value is 0 for undefined CPUCFG index | |
| * | |
| * Disable preemption since hw gcsr is accessed | |
| */ | |
| preempt_disable(); | |
| switch (index) { | |
| case 0 ... (KVM_MAX_CPUCFG_REGS - 1): | |
| vcpu->arch.gprs[rd] = vcpu->arch.cpucfg[index]; | |
| break; | |
| case CPUCFG_KVM_SIG: | |
| /* CPUCFG emulation between 0x40000000 -- 0x400000ff */ | |
| vcpu->arch.gprs[rd] = *(unsigned int *)KVM_SIGNATURE; | |
| break; | |
| case CPUCFG_KVM_FEATURE: | |
| ret = vcpu->kvm->arch.pv_features & LOONGARCH_PV_FEAT_MASK; | |
| vcpu->arch.gprs[rd] = ret; | |
| break; | |
| default: | |
| vcpu->arch.gprs[rd] = 0; | |
| break; | |
| } | |
| preempt_enable(); | |
| return EMULATE_DONE; | |
| } | |
| static unsigned long kvm_emu_read_csr(struct kvm_vcpu *vcpu, int csrid) | |
| { | |
| unsigned long val = 0; | |
| struct loongarch_csrs *csr = vcpu->arch.csr; | |
| /* | |
| * From LoongArch Reference Manual Volume 1 Chapter 4.2.1 | |
| * For undefined CSR id, return value is 0 | |
| */ | |
| if (get_gcsr_flag(csrid) & SW_GCSR) | |
| val = kvm_read_sw_gcsr(csr, csrid); | |
| else | |
| pr_warn_once("Unsupported csrrd 0x%x with pc %lx\n", csrid, vcpu->arch.pc); | |
| return val; | |
| } | |
| static unsigned long kvm_emu_write_csr(struct kvm_vcpu *vcpu, int csrid, unsigned long val) | |
| { | |
| unsigned long old = 0; | |
| struct loongarch_csrs *csr = vcpu->arch.csr; | |
| if (get_gcsr_flag(csrid) & SW_GCSR) { | |
| old = kvm_read_sw_gcsr(csr, csrid); | |
| kvm_write_sw_gcsr(csr, csrid, val); | |
| } else | |
| pr_warn_once("Unsupported csrwr 0x%x with pc %lx\n", csrid, vcpu->arch.pc); | |
| return old; | |
| } | |
| static unsigned long kvm_emu_xchg_csr(struct kvm_vcpu *vcpu, int csrid, | |
| unsigned long csr_mask, unsigned long val) | |
| { | |
| unsigned long old = 0; | |
| struct loongarch_csrs *csr = vcpu->arch.csr; | |
| if (get_gcsr_flag(csrid) & SW_GCSR) { | |
| old = kvm_read_sw_gcsr(csr, csrid); | |
| val = (old & ~csr_mask) | (val & csr_mask); | |
| kvm_write_sw_gcsr(csr, csrid, val); | |
| } else | |
| pr_warn_once("Unsupported csrxchg 0x%x with pc %lx\n", csrid, vcpu->arch.pc); | |
| return old; | |
| } | |
| static int kvm_handle_csr(struct kvm_vcpu *vcpu, larch_inst inst) | |
| { | |
| unsigned int rd, rj, csrid; | |
| unsigned long csr_mask, val = 0; | |
| /* | |
| * CSR value mask imm | |
| * rj = 0 means csrrd | |
| * rj = 1 means csrwr | |
| * rj != 0,1 means csrxchg | |
| */ | |
| rd = inst.reg2csr_format.rd; | |
| rj = inst.reg2csr_format.rj; | |
| csrid = inst.reg2csr_format.csr; | |
| if (csrid >= LOONGARCH_CSR_PERFCTRL0 && csrid <= vcpu->arch.max_pmu_csrid) { | |
| if (kvm_guest_has_pmu(&vcpu->arch)) { | |
| vcpu->arch.pc -= 4; | |
| kvm_make_request(KVM_REQ_PMU, vcpu); | |
| return EMULATE_DONE; | |
| } | |
| } | |
| /* Process CSR ops */ | |
| switch (rj) { | |
| case 0: /* process csrrd */ | |
| val = kvm_emu_read_csr(vcpu, csrid); | |
| vcpu->arch.gprs[rd] = val; | |
| break; | |
| case 1: /* process csrwr */ | |
| val = vcpu->arch.gprs[rd]; | |
| val = kvm_emu_write_csr(vcpu, csrid, val); | |
| vcpu->arch.gprs[rd] = val; | |
| break; | |
| default: /* process csrxchg */ | |
| val = vcpu->arch.gprs[rd]; | |
| csr_mask = vcpu->arch.gprs[rj]; | |
| val = kvm_emu_xchg_csr(vcpu, csrid, csr_mask, val); | |
| vcpu->arch.gprs[rd] = val; | |
| } | |
| return EMULATE_DONE; | |
| } | |
| int kvm_emu_iocsr(larch_inst inst, struct kvm_run *run, struct kvm_vcpu *vcpu) | |
| { | |
| int idx, ret; | |
| unsigned long *val; | |
| u32 addr, rd, rj, opcode; | |
| /* | |
| * Each IOCSR with different opcode | |
| */ | |
| rd = inst.reg2_format.rd; | |
| rj = inst.reg2_format.rj; | |
| opcode = inst.reg2_format.opcode; | |
| addr = vcpu->arch.gprs[rj]; | |
| run->iocsr_io.phys_addr = addr; | |
| run->iocsr_io.is_write = 0; | |
| val = &vcpu->arch.gprs[rd]; | |
| /* LoongArch is Little endian */ | |
| switch (opcode) { | |
| case iocsrrdb_op: | |
| run->iocsr_io.len = 1; | |
| break; | |
| case iocsrrdh_op: | |
| run->iocsr_io.len = 2; | |
| break; | |
| case iocsrrdw_op: | |
| run->iocsr_io.len = 4; | |
| break; | |
| case iocsrrdd_op: | |
| run->iocsr_io.len = 8; | |
| break; | |
| case iocsrwrb_op: | |
| run->iocsr_io.len = 1; | |
| run->iocsr_io.is_write = 1; | |
| break; | |
| case iocsrwrh_op: | |
| run->iocsr_io.len = 2; | |
| run->iocsr_io.is_write = 1; | |
| break; | |
| case iocsrwrw_op: | |
| run->iocsr_io.len = 4; | |
| run->iocsr_io.is_write = 1; | |
| break; | |
| case iocsrwrd_op: | |
| run->iocsr_io.len = 8; | |
| run->iocsr_io.is_write = 1; | |
| break; | |
| default: | |
| return EMULATE_FAIL; | |
| } | |
| if (run->iocsr_io.is_write) { | |
| idx = srcu_read_lock(&vcpu->kvm->srcu); | |
| ret = kvm_io_bus_write(vcpu, KVM_IOCSR_BUS, addr, run->iocsr_io.len, val); | |
| srcu_read_unlock(&vcpu->kvm->srcu, idx); | |
| if (ret == 0) | |
| ret = EMULATE_DONE; | |
| else { | |
| ret = EMULATE_DO_IOCSR; | |
| /* Save data and let user space to write it */ | |
| memcpy(run->iocsr_io.data, val, run->iocsr_io.len); | |
| } | |
| trace_kvm_iocsr(KVM_TRACE_IOCSR_WRITE, run->iocsr_io.len, addr, val); | |
| } else { | |
| vcpu->arch.io_gpr = rd; /* Set register id for iocsr read completion */ | |
| idx = srcu_read_lock(&vcpu->kvm->srcu); | |
| ret = kvm_io_bus_read(vcpu, KVM_IOCSR_BUS, addr, | |
| run->iocsr_io.len, run->iocsr_io.data); | |
| srcu_read_unlock(&vcpu->kvm->srcu, idx); | |
| if (ret == 0) { | |
| kvm_complete_iocsr_read(vcpu, run); | |
| ret = EMULATE_DONE; | |
| } else | |
| ret = EMULATE_DO_IOCSR; | |
| trace_kvm_iocsr(KVM_TRACE_IOCSR_READ, run->iocsr_io.len, addr, NULL); | |
| } | |
| return ret; | |
| } | |
| int kvm_complete_iocsr_read(struct kvm_vcpu *vcpu, struct kvm_run *run) | |
| { | |
| enum emulation_result er = EMULATE_DONE; | |
| unsigned long *gpr = &vcpu->arch.gprs[vcpu->arch.io_gpr]; | |
| switch (run->iocsr_io.len) { | |
| case 1: | |
| *gpr = *(s8 *)run->iocsr_io.data; | |
| break; | |
| case 2: | |
| *gpr = *(s16 *)run->iocsr_io.data; | |
| break; | |
| case 4: | |
| *gpr = *(s32 *)run->iocsr_io.data; | |
| break; | |
| case 8: | |
| *gpr = *(s64 *)run->iocsr_io.data; | |
| break; | |
| default: | |
| kvm_pr_unimpl("Bad IOCSR length: %d, addr is 0x%lx\n", | |
| run->iocsr_io.len, vcpu->arch.badv); | |
| er = EMULATE_FAIL; | |
| break; | |
| } | |
| return er; | |
| } | |
| int kvm_emu_idle(struct kvm_vcpu *vcpu) | |
| { | |
| ++vcpu->stat.idle_exits; | |
| trace_kvm_exit_idle(vcpu, KVM_TRACE_EXIT_IDLE); | |
| if (!kvm_arch_vcpu_runnable(vcpu)) | |
| kvm_vcpu_halt(vcpu); | |
| return EMULATE_DONE; | |
| } | |
| static int kvm_trap_handle_gspr(struct kvm_vcpu *vcpu) | |
| { | |
| unsigned long curr_pc; | |
| larch_inst inst; | |
| enum emulation_result er = EMULATE_DONE; | |
| struct kvm_run *run = vcpu->run; | |
| /* Fetch the instruction */ | |
| inst.word = vcpu->arch.badi; | |
| curr_pc = vcpu->arch.pc; | |
| update_pc(&vcpu->arch); | |
| trace_kvm_exit_gspr(vcpu, inst.word); | |
| er = EMULATE_FAIL; | |
| switch (((inst.word >> 24) & 0xff)) { | |
| case 0x0: /* CPUCFG GSPR */ | |
| trace_kvm_exit_cpucfg(vcpu, KVM_TRACE_EXIT_CPUCFG); | |
| er = kvm_emu_cpucfg(vcpu, inst); | |
| break; | |
| case 0x4: /* CSR{RD,WR,XCHG} GSPR */ | |
| trace_kvm_exit_csr(vcpu, KVM_TRACE_EXIT_CSR); | |
| er = kvm_handle_csr(vcpu, inst); | |
| break; | |
| case 0x6: /* Cache, Idle and IOCSR GSPR */ | |
| switch (((inst.word >> 22) & 0x3ff)) { | |
| case 0x18: /* Cache GSPR */ | |
| er = EMULATE_DONE; | |
| trace_kvm_exit_cache(vcpu, KVM_TRACE_EXIT_CACHE); | |
| break; | |
| case 0x19: /* Idle/IOCSR GSPR */ | |
| switch (((inst.word >> 15) & 0x1ffff)) { | |
| case 0xc90: /* IOCSR GSPR */ | |
| er = kvm_emu_iocsr(inst, run, vcpu); | |
| break; | |
| case 0xc91: /* Idle GSPR */ | |
| er = kvm_emu_idle(vcpu); | |
| break; | |
| default: | |
| er = EMULATE_FAIL; | |
| break; | |
| } | |
| break; | |
| default: | |
| er = EMULATE_FAIL; | |
| break; | |
| } | |
| break; | |
| default: | |
| er = EMULATE_FAIL; | |
| break; | |
| } | |
| /* Rollback PC only if emulation was unsuccessful */ | |
| if (er == EMULATE_FAIL) { | |
| kvm_pr_unimpl("[%#lx]%s: unsupported gspr instruction 0x%08x\n", | |
| curr_pc, __func__, inst.word); | |
| kvm_arch_vcpu_dump_regs(vcpu); | |
| vcpu->arch.pc = curr_pc; | |
| } | |
| return er; | |
| } | |
| /* | |
| * Trigger GSPR: | |
| * 1) Execute CPUCFG instruction; | |
| * 2) Execute CACOP/IDLE instructions; | |
| * 3) Access to unimplemented CSRs/IOCSRs. | |
| */ | |
| static int kvm_handle_gspr(struct kvm_vcpu *vcpu, int ecode) | |
| { | |
| int ret = RESUME_GUEST; | |
| enum emulation_result er = EMULATE_DONE; | |
| er = kvm_trap_handle_gspr(vcpu); | |
| if (er == EMULATE_DONE) { | |
| ret = RESUME_GUEST; | |
| } else if (er == EMULATE_DO_MMIO) { | |
| vcpu->run->exit_reason = KVM_EXIT_MMIO; | |
| ret = RESUME_HOST; | |
| } else if (er == EMULATE_DO_IOCSR) { | |
| vcpu->run->exit_reason = KVM_EXIT_LOONGARCH_IOCSR; | |
| ret = RESUME_HOST; | |
| } else { | |
| kvm_queue_exception(vcpu, EXCCODE_INE, 0); | |
| ret = RESUME_GUEST; | |
| } | |
| return ret; | |
| } | |
| int kvm_emu_mmio_read(struct kvm_vcpu *vcpu, larch_inst inst) | |
| { | |
| int idx, ret; | |
| unsigned int op8, opcode, rd; | |
| struct kvm_run *run = vcpu->run; | |
| run->mmio.phys_addr = vcpu->arch.badv; | |
| vcpu->mmio_needed = 2; /* signed */ | |
| op8 = (inst.word >> 24) & 0xff; | |
| ret = EMULATE_DO_MMIO; | |
| switch (op8) { | |
| case 0x24 ... 0x27: /* ldptr.w/d process */ | |
| rd = inst.reg2i14_format.rd; | |
| opcode = inst.reg2i14_format.opcode; | |
| switch (opcode) { | |
| case ldptrw_op: | |
| run->mmio.len = 4; | |
| break; | |
| case ldptrd_op: | |
| run->mmio.len = 8; | |
| break; | |
| default: | |
| ret = EMULATE_FAIL; | |
| break; | |
| } | |
| break; | |
| case 0x28 ... 0x2e: /* ld.b/h/w/d, ld.bu/hu/wu process */ | |
| rd = inst.reg2i12_format.rd; | |
| opcode = inst.reg2i12_format.opcode; | |
| switch (opcode) { | |
| case ldb_op: | |
| run->mmio.len = 1; | |
| break; | |
| case ldbu_op: | |
| vcpu->mmio_needed = 1; /* unsigned */ | |
| run->mmio.len = 1; | |
| break; | |
| case ldh_op: | |
| run->mmio.len = 2; | |
| break; | |
| case ldhu_op: | |
| vcpu->mmio_needed = 1; /* unsigned */ | |
| run->mmio.len = 2; | |
| break; | |
| case ldw_op: | |
| run->mmio.len = 4; | |
| break; | |
| case ldwu_op: | |
| vcpu->mmio_needed = 1; /* unsigned */ | |
| run->mmio.len = 4; | |
| break; | |
| case ldd_op: | |
| run->mmio.len = 8; | |
| break; | |
| default: | |
| ret = EMULATE_FAIL; | |
| break; | |
| } | |
| break; | |
| case 0x38: /* ldx.b/h/w/d, ldx.bu/hu/wu process */ | |
| rd = inst.reg3_format.rd; | |
| opcode = inst.reg3_format.opcode; | |
| switch (opcode) { | |
| case ldxb_op: | |
| run->mmio.len = 1; | |
| break; | |
| case ldxbu_op: | |
| run->mmio.len = 1; | |
| vcpu->mmio_needed = 1; /* unsigned */ | |
| break; | |
| case ldxh_op: | |
| run->mmio.len = 2; | |
| break; | |
| case ldxhu_op: | |
| run->mmio.len = 2; | |
| vcpu->mmio_needed = 1; /* unsigned */ | |
| break; | |
| case ldxw_op: | |
| run->mmio.len = 4; | |
| break; | |
| case ldxwu_op: | |
| run->mmio.len = 4; | |
| vcpu->mmio_needed = 1; /* unsigned */ | |
| break; | |
| case ldxd_op: | |
| run->mmio.len = 8; | |
| break; | |
| default: | |
| ret = EMULATE_FAIL; | |
| break; | |
| } | |
| break; | |
| default: | |
| ret = EMULATE_FAIL; | |
| } | |
| if (ret == EMULATE_DO_MMIO) { | |
| trace_kvm_mmio(KVM_TRACE_MMIO_READ, run->mmio.len, run->mmio.phys_addr, NULL); | |
| vcpu->arch.io_gpr = rd; /* Set for kvm_complete_mmio_read() use */ | |
| /* | |
| * If mmio device such as PCH-PIC is emulated in KVM, | |
| * it need not return to user space to handle the mmio | |
| * exception. | |
| */ | |
| idx = srcu_read_lock(&vcpu->kvm->srcu); | |
| ret = kvm_io_bus_read(vcpu, KVM_MMIO_BUS, vcpu->arch.badv, | |
| run->mmio.len, run->mmio.data); | |
| srcu_read_unlock(&vcpu->kvm->srcu, idx); | |
| if (!ret) { | |
| kvm_complete_mmio_read(vcpu, run); | |
| vcpu->mmio_needed = 0; | |
| return EMULATE_DONE; | |
| } | |
| run->mmio.is_write = 0; | |
| vcpu->mmio_is_write = 0; | |
| return EMULATE_DO_MMIO; | |
| } | |
| kvm_pr_unimpl("Read not supported Inst=0x%08x @%lx BadVaddr:%#lx\n", | |
| inst.word, vcpu->arch.pc, vcpu->arch.badv); | |
| kvm_arch_vcpu_dump_regs(vcpu); | |
| vcpu->mmio_needed = 0; | |
| return ret; | |
| } | |
| int kvm_complete_mmio_read(struct kvm_vcpu *vcpu, struct kvm_run *run) | |
| { | |
| enum emulation_result er = EMULATE_DONE; | |
| unsigned long *gpr = &vcpu->arch.gprs[vcpu->arch.io_gpr]; | |
| /* Update with new PC */ | |
| update_pc(&vcpu->arch); | |
| switch (run->mmio.len) { | |
| case 1: | |
| if (vcpu->mmio_needed == 2) | |
| *gpr = *(s8 *)run->mmio.data; | |
| else | |
| *gpr = *(u8 *)run->mmio.data; | |
| break; | |
| case 2: | |
| if (vcpu->mmio_needed == 2) | |
| *gpr = *(s16 *)run->mmio.data; | |
| else | |
| *gpr = *(u16 *)run->mmio.data; | |
| break; | |
| case 4: | |
| if (vcpu->mmio_needed == 2) | |
| *gpr = *(s32 *)run->mmio.data; | |
| else | |
| *gpr = *(u32 *)run->mmio.data; | |
| break; | |
| case 8: | |
| *gpr = *(s64 *)run->mmio.data; | |
| break; | |
| default: | |
| kvm_pr_unimpl("Bad MMIO length: %d, addr is 0x%lx\n", | |
| run->mmio.len, vcpu->arch.badv); | |
| er = EMULATE_FAIL; | |
| break; | |
| } | |
| trace_kvm_mmio(KVM_TRACE_MMIO_READ, run->mmio.len, | |
| run->mmio.phys_addr, run->mmio.data); | |
| return er; | |
| } | |
| int kvm_emu_mmio_write(struct kvm_vcpu *vcpu, larch_inst inst) | |
| { | |
| int idx, ret; | |
| unsigned int rd, op8, opcode; | |
| unsigned long curr_pc, rd_val = 0; | |
| struct kvm_run *run = vcpu->run; | |
| void *data = run->mmio.data; | |
| /* | |
| * Update PC and hold onto current PC in case there is | |
| * an error and we want to rollback the PC | |
| */ | |
| curr_pc = vcpu->arch.pc; | |
| update_pc(&vcpu->arch); | |
| op8 = (inst.word >> 24) & 0xff; | |
| run->mmio.phys_addr = vcpu->arch.badv; | |
| ret = EMULATE_DO_MMIO; | |
| switch (op8) { | |
| case 0x24 ... 0x27: /* stptr.w/d process */ | |
| rd = inst.reg2i14_format.rd; | |
| opcode = inst.reg2i14_format.opcode; | |
| switch (opcode) { | |
| case stptrw_op: | |
| run->mmio.len = 4; | |
| *(unsigned int *)data = vcpu->arch.gprs[rd]; | |
| break; | |
| case stptrd_op: | |
| run->mmio.len = 8; | |
| *(unsigned long *)data = vcpu->arch.gprs[rd]; | |
| break; | |
| default: | |
| ret = EMULATE_FAIL; | |
| break; | |
| } | |
| break; | |
| case 0x28 ... 0x2e: /* st.b/h/w/d process */ | |
| rd = inst.reg2i12_format.rd; | |
| opcode = inst.reg2i12_format.opcode; | |
| rd_val = vcpu->arch.gprs[rd]; | |
| switch (opcode) { | |
| case stb_op: | |
| run->mmio.len = 1; | |
| *(unsigned char *)data = rd_val; | |
| break; | |
| case sth_op: | |
| run->mmio.len = 2; | |
| *(unsigned short *)data = rd_val; | |
| break; | |
| case stw_op: | |
| run->mmio.len = 4; | |
| *(unsigned int *)data = rd_val; | |
| break; | |
| case std_op: | |
| run->mmio.len = 8; | |
| *(unsigned long *)data = rd_val; | |
| break; | |
| default: | |
| ret = EMULATE_FAIL; | |
| break; | |
| } | |
| break; | |
| case 0x38: /* stx.b/h/w/d process */ | |
| rd = inst.reg3_format.rd; | |
| opcode = inst.reg3_format.opcode; | |
| switch (opcode) { | |
| case stxb_op: | |
| run->mmio.len = 1; | |
| *(unsigned char *)data = vcpu->arch.gprs[rd]; | |
| break; | |
| case stxh_op: | |
| run->mmio.len = 2; | |
| *(unsigned short *)data = vcpu->arch.gprs[rd]; | |
| break; | |
| case stxw_op: | |
| run->mmio.len = 4; | |
| *(unsigned int *)data = vcpu->arch.gprs[rd]; | |
| break; | |
| case stxd_op: | |
| run->mmio.len = 8; | |
| *(unsigned long *)data = vcpu->arch.gprs[rd]; | |
| break; | |
| default: | |
| ret = EMULATE_FAIL; | |
| break; | |
| } | |
| break; | |
| default: | |
| ret = EMULATE_FAIL; | |
| } | |
| if (ret == EMULATE_DO_MMIO) { | |
| trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, run->mmio.len, run->mmio.phys_addr, data); | |
| /* | |
| * If mmio device such as PCH-PIC is emulated in KVM, | |
| * it need not return to user space to handle the mmio | |
| * exception. | |
| */ | |
| idx = srcu_read_lock(&vcpu->kvm->srcu); | |
| ret = kvm_io_bus_write(vcpu, KVM_MMIO_BUS, vcpu->arch.badv, run->mmio.len, data); | |
| srcu_read_unlock(&vcpu->kvm->srcu, idx); | |
| if (!ret) | |
| return EMULATE_DONE; | |
| run->mmio.is_write = 1; | |
| vcpu->mmio_needed = 1; | |
| vcpu->mmio_is_write = 1; | |
| return EMULATE_DO_MMIO; | |
| } | |
| vcpu->arch.pc = curr_pc; | |
| kvm_pr_unimpl("Write not supported Inst=0x%08x @%lx BadVaddr:%#lx\n", | |
| inst.word, vcpu->arch.pc, vcpu->arch.badv); | |
| kvm_arch_vcpu_dump_regs(vcpu); | |
| /* Rollback PC if emulation was unsuccessful */ | |
| return ret; | |
| } | |
| static int kvm_handle_rdwr_fault(struct kvm_vcpu *vcpu, bool write, int ecode) | |
| { | |
| int ret; | |
| larch_inst inst; | |
| enum emulation_result er = EMULATE_DONE; | |
| struct kvm_run *run = vcpu->run; | |
| unsigned long badv = vcpu->arch.badv; | |
| /* Inject ADE exception if exceed max GPA size */ | |
| if (unlikely(badv >= vcpu->kvm->arch.gpa_size)) { | |
| kvm_queue_exception(vcpu, EXCCODE_ADE, EXSUBCODE_ADEM); | |
| return RESUME_GUEST; | |
| } | |
| ret = kvm_handle_mm_fault(vcpu, badv, write, ecode); | |
| if (ret) { | |
| /* Treat as MMIO */ | |
| inst.word = vcpu->arch.badi; | |
| if (write) { | |
| er = kvm_emu_mmio_write(vcpu, inst); | |
| } else { | |
| /* A code fetch fault doesn't count as an MMIO */ | |
| if (kvm_is_ifetch_fault(&vcpu->arch)) { | |
| kvm_queue_exception(vcpu, EXCCODE_ADE, EXSUBCODE_ADEF); | |
| return RESUME_GUEST; | |
| } | |
| er = kvm_emu_mmio_read(vcpu, inst); | |
| } | |
| } | |
| if (er == EMULATE_DONE) { | |
| ret = RESUME_GUEST; | |
| } else if (er == EMULATE_DO_MMIO) { | |
| run->exit_reason = KVM_EXIT_MMIO; | |
| ret = RESUME_HOST; | |
| } else { | |
| kvm_queue_exception(vcpu, EXCCODE_ADE, EXSUBCODE_ADEM); | |
| ret = RESUME_GUEST; | |
| } | |
| return ret; | |
| } | |
| static int kvm_handle_read_fault(struct kvm_vcpu *vcpu, int ecode) | |
| { | |
| return kvm_handle_rdwr_fault(vcpu, false, ecode); | |
| } | |
| static int kvm_handle_write_fault(struct kvm_vcpu *vcpu, int ecode) | |
| { | |
| return kvm_handle_rdwr_fault(vcpu, true, ecode); | |
| } | |
| int kvm_complete_user_service(struct kvm_vcpu *vcpu, struct kvm_run *run) | |
| { | |
| update_pc(&vcpu->arch); | |
| kvm_write_reg(vcpu, LOONGARCH_GPR_A0, run->hypercall.ret); | |
| return 0; | |
| } | |
| /** | |
| * kvm_handle_fpu_disabled() - Guest used fpu however it is disabled at host | |
| * @vcpu: Virtual CPU context. | |
| * @ecode: Exception code. | |
| * | |
| * Handle when the guest attempts to use fpu which hasn't been allowed | |
| * by the root context. | |
| */ | |
| static int kvm_handle_fpu_disabled(struct kvm_vcpu *vcpu, int ecode) | |
| { | |
| struct kvm_run *run = vcpu->run; | |
| if (!kvm_guest_has_fpu(&vcpu->arch)) { | |
| kvm_queue_exception(vcpu, EXCCODE_INE, 0); | |
| return RESUME_GUEST; | |
| } | |
| /* | |
| * If guest FPU not present, the FPU operation should have been | |
| * treated as a reserved instruction! | |
| * If FPU already in use, we shouldn't get this at all. | |
| */ | |
| if (vcpu->arch.aux_inuse & KVM_LARCH_FPU) { | |
| kvm_pr_unimpl("%s internal error\n", __func__); | |
| run->exit_reason = KVM_EXIT_INTERNAL_ERROR; | |
| return RESUME_HOST; | |
| } | |
| kvm_make_request(KVM_REQ_FPU_LOAD, vcpu); | |
| return RESUME_GUEST; | |
| } | |
| static long kvm_save_notify(struct kvm_vcpu *vcpu) | |
| { | |
| unsigned long id, data; | |
| id = kvm_read_reg(vcpu, LOONGARCH_GPR_A1); | |
| data = kvm_read_reg(vcpu, LOONGARCH_GPR_A2); | |
| switch (id) { | |
| case BIT(KVM_FEATURE_STEAL_TIME): | |
| if (data & ~(KVM_STEAL_PHYS_MASK | KVM_STEAL_PHYS_VALID)) | |
| return KVM_HCALL_INVALID_PARAMETER; | |
| vcpu->arch.st.guest_addr = data; | |
| if (!(data & KVM_STEAL_PHYS_VALID)) | |
| return 0; | |
| vcpu->arch.st.last_steal = current->sched_info.run_delay; | |
| kvm_make_request(KVM_REQ_STEAL_UPDATE, vcpu); | |
| return 0; | |
| default: | |
| return KVM_HCALL_INVALID_CODE; | |
| } | |
| } | |
| /* | |
| * kvm_handle_lsx_disabled() - Guest used LSX while disabled in root. | |
| * @vcpu: Virtual CPU context. | |
| * @ecode: Exception code. | |
| * | |
| * Handle when the guest attempts to use LSX when it is disabled in the root | |
| * context. | |
| */ | |
| static int kvm_handle_lsx_disabled(struct kvm_vcpu *vcpu, int ecode) | |
| { | |
| if (!kvm_guest_has_lsx(&vcpu->arch)) | |
| kvm_queue_exception(vcpu, EXCCODE_INE, 0); | |
| else | |
| kvm_make_request(KVM_REQ_FPU_LOAD, vcpu); | |
| return RESUME_GUEST; | |
| } | |
| /* | |
| * kvm_handle_lasx_disabled() - Guest used LASX while disabled in root. | |
| * @vcpu: Virtual CPU context. | |
| * @ecode: Exception code. | |
| * | |
| * Handle when the guest attempts to use LASX when it is disabled in the root | |
| * context. | |
| */ | |
| static int kvm_handle_lasx_disabled(struct kvm_vcpu *vcpu, int ecode) | |
| { | |
| if (!kvm_guest_has_lasx(&vcpu->arch)) | |
| kvm_queue_exception(vcpu, EXCCODE_INE, 0); | |
| else | |
| kvm_make_request(KVM_REQ_FPU_LOAD, vcpu); | |
| return RESUME_GUEST; | |
| } | |
| static int kvm_handle_lbt_disabled(struct kvm_vcpu *vcpu, int ecode) | |
| { | |
| if (!kvm_guest_has_lbt(&vcpu->arch)) | |
| kvm_queue_exception(vcpu, EXCCODE_INE, 0); | |
| else | |
| kvm_make_request(KVM_REQ_LBT_LOAD, vcpu); | |
| return RESUME_GUEST; | |
| } | |
| static void kvm_send_pv_ipi(struct kvm_vcpu *vcpu) | |
| { | |
| unsigned int min, cpu; | |
| struct kvm_vcpu *dest; | |
| DECLARE_BITMAP(ipi_bitmap, BITS_PER_LONG * 2) = { | |
| kvm_read_reg(vcpu, LOONGARCH_GPR_A1), | |
| kvm_read_reg(vcpu, LOONGARCH_GPR_A2) | |
| }; | |
| min = kvm_read_reg(vcpu, LOONGARCH_GPR_A3); | |
| for_each_set_bit(cpu, ipi_bitmap, BITS_PER_LONG * 2) { | |
| dest = kvm_get_vcpu_by_cpuid(vcpu->kvm, cpu + min); | |
| if (!dest) | |
| continue; | |
| /* Send SWI0 to dest vcpu to emulate IPI interrupt */ | |
| kvm_queue_irq(dest, INT_SWI0); | |
| kvm_vcpu_kick(dest); | |
| } | |
| } | |
| /* | |
| * Hypercall emulation always return to guest, Caller should check retval. | |
| */ | |
| static void kvm_handle_service(struct kvm_vcpu *vcpu) | |
| { | |
| long ret = KVM_HCALL_INVALID_CODE; | |
| unsigned long func = kvm_read_reg(vcpu, LOONGARCH_GPR_A0); | |
| switch (func) { | |
| case KVM_HCALL_FUNC_IPI: | |
| if (kvm_guest_has_pv_feature(vcpu, KVM_FEATURE_IPI)) { | |
| kvm_send_pv_ipi(vcpu); | |
| ret = KVM_HCALL_SUCCESS; | |
| } | |
| break; | |
| case KVM_HCALL_FUNC_NOTIFY: | |
| if (kvm_guest_has_pv_feature(vcpu, KVM_FEATURE_STEAL_TIME)) | |
| ret = kvm_save_notify(vcpu); | |
| break; | |
| default: | |
| break; | |
| } | |
| kvm_write_reg(vcpu, LOONGARCH_GPR_A0, ret); | |
| } | |
| static int kvm_handle_hypercall(struct kvm_vcpu *vcpu, int ecode) | |
| { | |
| int ret; | |
| larch_inst inst; | |
| unsigned int code; | |
| inst.word = vcpu->arch.badi; | |
| code = inst.reg0i15_format.immediate; | |
| ret = RESUME_GUEST; | |
| switch (code) { | |
| case KVM_HCALL_SERVICE: | |
| vcpu->stat.hypercall_exits++; | |
| kvm_handle_service(vcpu); | |
| break; | |
| case KVM_HCALL_USER_SERVICE: | |
| if (!kvm_guest_has_pv_feature(vcpu, KVM_FEATURE_USER_HCALL)) { | |
| kvm_write_reg(vcpu, LOONGARCH_GPR_A0, KVM_HCALL_INVALID_CODE); | |
| break; | |
| } | |
| vcpu->stat.hypercall_exits++; | |
| vcpu->run->exit_reason = KVM_EXIT_HYPERCALL; | |
| vcpu->run->hypercall.nr = KVM_HCALL_USER_SERVICE; | |
| vcpu->run->hypercall.args[0] = kvm_read_reg(vcpu, LOONGARCH_GPR_A0); | |
| vcpu->run->hypercall.args[1] = kvm_read_reg(vcpu, LOONGARCH_GPR_A1); | |
| vcpu->run->hypercall.args[2] = kvm_read_reg(vcpu, LOONGARCH_GPR_A2); | |
| vcpu->run->hypercall.args[3] = kvm_read_reg(vcpu, LOONGARCH_GPR_A3); | |
| vcpu->run->hypercall.args[4] = kvm_read_reg(vcpu, LOONGARCH_GPR_A4); | |
| vcpu->run->hypercall.args[5] = kvm_read_reg(vcpu, LOONGARCH_GPR_A5); | |
| vcpu->run->hypercall.flags = 0; | |
| /* | |
| * Set invalid return value by default, let user-mode VMM modify it. | |
| */ | |
| vcpu->run->hypercall.ret = KVM_HCALL_INVALID_CODE; | |
| ret = RESUME_HOST; | |
| break; | |
| case KVM_HCALL_SWDBG: | |
| /* KVM_HCALL_SWDBG only in effective when SW_BP is enabled */ | |
| if (vcpu->guest_debug & KVM_GUESTDBG_SW_BP_MASK) { | |
| vcpu->run->exit_reason = KVM_EXIT_DEBUG; | |
| ret = RESUME_HOST; | |
| break; | |
| } | |
| fallthrough; | |
| default: | |
| /* Treat it as noop intruction, only set return value */ | |
| kvm_write_reg(vcpu, LOONGARCH_GPR_A0, KVM_HCALL_INVALID_CODE); | |
| break; | |
| } | |
| if (ret == RESUME_GUEST) | |
| update_pc(&vcpu->arch); | |
| return ret; | |
| } | |
| /* | |
| * LoongArch KVM callback handling for unimplemented guest exiting | |
| */ | |
| static int kvm_fault_ni(struct kvm_vcpu *vcpu, int ecode) | |
| { | |
| unsigned int inst; | |
| unsigned long badv; | |
| /* Fetch the instruction */ | |
| inst = vcpu->arch.badi; | |
| badv = vcpu->arch.badv; | |
| kvm_pr_unimpl("ECode: %d PC=%#lx Inst=0x%08x BadVaddr=%#lx ESTAT=%#lx\n", | |
| ecode, vcpu->arch.pc, inst, badv, read_gcsr_estat()); | |
| kvm_arch_vcpu_dump_regs(vcpu); | |
| kvm_queue_exception(vcpu, EXCCODE_INE, 0); | |
| return RESUME_GUEST; | |
| } | |
| static exit_handle_fn kvm_fault_tables[EXCCODE_INT_START] = { | |
| [0 ... EXCCODE_INT_START - 1] = kvm_fault_ni, | |
| [EXCCODE_TLBI] = kvm_handle_read_fault, | |
| [EXCCODE_TLBL] = kvm_handle_read_fault, | |
| [EXCCODE_TLBS] = kvm_handle_write_fault, | |
| [EXCCODE_TLBM] = kvm_handle_write_fault, | |
| [EXCCODE_FPDIS] = kvm_handle_fpu_disabled, | |
| [EXCCODE_LSXDIS] = kvm_handle_lsx_disabled, | |
| [EXCCODE_LASXDIS] = kvm_handle_lasx_disabled, | |
| [EXCCODE_BTDIS] = kvm_handle_lbt_disabled, | |
| [EXCCODE_GSPR] = kvm_handle_gspr, | |
| [EXCCODE_HVC] = kvm_handle_hypercall, | |
| }; | |
| int kvm_handle_fault(struct kvm_vcpu *vcpu, int fault) | |
| { | |
| return kvm_fault_tables[fault](vcpu, fault); | |
| } |