diff options
Diffstat (limited to 'arch/loongarch/kvm')
-rw-r--r-- | arch/loongarch/kvm/Kconfig | 1 | ||||
-rw-r--r-- | arch/loongarch/kvm/Makefile | 4 | ||||
-rw-r--r-- | arch/loongarch/kvm/exit.c | 106 | ||||
-rw-r--r-- | arch/loongarch/kvm/intc/eiointc.c | 618 | ||||
-rw-r--r-- | arch/loongarch/kvm/intc/ipi.c | 32 | ||||
-rw-r--r-- | arch/loongarch/kvm/intc/pch_pic.c | 4 | ||||
-rw-r--r-- | arch/loongarch/kvm/interrupt.c | 25 | ||||
-rw-r--r-- | arch/loongarch/kvm/main.c | 36 | ||||
-rw-r--r-- | arch/loongarch/kvm/mmu.c | 15 | ||||
-rw-r--r-- | arch/loongarch/kvm/switch.S | 14 | ||||
-rw-r--r-- | arch/loongarch/kvm/trace.h | 14 | ||||
-rw-r--r-- | arch/loongarch/kvm/vcpu.c | 69 | ||||
-rw-r--r-- | arch/loongarch/kvm/vm.c | 6 |
13 files changed, 340 insertions, 604 deletions
diff --git a/arch/loongarch/kvm/Kconfig b/arch/loongarch/kvm/Kconfig index 97a811077ac3..40eea6da7c25 100644 --- a/arch/loongarch/kvm/Kconfig +++ b/arch/loongarch/kvm/Kconfig @@ -33,6 +33,7 @@ config KVM select KVM_MMIO select KVM_XFER_TO_GUEST_WORK select SCHED_INFO + select GUEST_PERF_EVENTS if PERF_EVENTS help Support hosting virtualized guest machines using hardware virtualization extensions. You will need diff --git a/arch/loongarch/kvm/Makefile b/arch/loongarch/kvm/Makefile index 3a01292f71cc..cb41d9265662 100644 --- a/arch/loongarch/kvm/Makefile +++ b/arch/loongarch/kvm/Makefile @@ -3,8 +3,6 @@ # Makefile for LoongArch KVM support # -ccflags-y += -I $(src) - include $(srctree)/virt/kvm/Makefile.kvm obj-$(CONFIG_KVM) += kvm.o @@ -23,4 +21,4 @@ kvm-y += intc/eiointc.o kvm-y += intc/pch_pic.o kvm-y += irqfd.o -CFLAGS_exit.o += $(call cc-option,-Wno-override-init,) +CFLAGS_exit.o += $(call cc-disable-warning, override-init) diff --git a/arch/loongarch/kvm/exit.c b/arch/loongarch/kvm/exit.c index a7893bd01e73..2ce41f93b2a4 100644 --- a/arch/loongarch/kvm/exit.c +++ b/arch/loongarch/kvm/exit.c @@ -289,9 +289,11 @@ static int kvm_trap_handle_gspr(struct kvm_vcpu *vcpu) 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 */ @@ -341,7 +343,7 @@ static int kvm_trap_handle_gspr(struct kvm_vcpu *vcpu) * 2) Execute CACOP/IDLE instructions; * 3) Access to unimplemented CSRs/IOCSRs. */ -static int kvm_handle_gspr(struct kvm_vcpu *vcpu) +static int kvm_handle_gspr(struct kvm_vcpu *vcpu, int ecode) { int ret = RESUME_GUEST; enum emulation_result er = EMULATE_DONE; @@ -661,7 +663,7 @@ int kvm_emu_mmio_write(struct kvm_vcpu *vcpu, larch_inst inst) return ret; } -static int kvm_handle_rdwr_fault(struct kvm_vcpu *vcpu, bool write) +static int kvm_handle_rdwr_fault(struct kvm_vcpu *vcpu, bool write, int ecode) { int ret; larch_inst inst; @@ -669,7 +671,13 @@ static int kvm_handle_rdwr_fault(struct kvm_vcpu *vcpu, bool write) struct kvm_run *run = vcpu->run; unsigned long badv = vcpu->arch.badv; - ret = kvm_handle_mm_fault(vcpu, badv, write); + /* 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; @@ -699,24 +707,33 @@ static int kvm_handle_rdwr_fault(struct kvm_vcpu *vcpu, bool write) return ret; } -static int kvm_handle_read_fault(struct kvm_vcpu *vcpu) +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, false); + return kvm_handle_rdwr_fault(vcpu, true, ecode); } -static int kvm_handle_write_fault(struct kvm_vcpu *vcpu) +int kvm_complete_user_service(struct kvm_vcpu *vcpu, struct kvm_run *run) { - return kvm_handle_rdwr_fault(vcpu, true); + 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) +static int kvm_handle_fpu_disabled(struct kvm_vcpu *vcpu, int ecode) { struct kvm_run *run = vcpu->run; @@ -769,11 +786,12 @@ static long kvm_save_notify(struct kvm_vcpu *vcpu) /* * 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) +static int kvm_handle_lsx_disabled(struct kvm_vcpu *vcpu, int ecode) { if (kvm_own_lsx(vcpu)) kvm_queue_exception(vcpu, EXCCODE_INE, 0); @@ -784,11 +802,12 @@ static int kvm_handle_lsx_disabled(struct kvm_vcpu *vcpu) /* * 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) +static int kvm_handle_lasx_disabled(struct kvm_vcpu *vcpu, int ecode) { if (kvm_own_lasx(vcpu)) kvm_queue_exception(vcpu, EXCCODE_INE, 0); @@ -796,7 +815,7 @@ static int kvm_handle_lasx_disabled(struct kvm_vcpu *vcpu) return RESUME_GUEST; } -static int kvm_handle_lbt_disabled(struct kvm_vcpu *vcpu) +static int kvm_handle_lbt_disabled(struct kvm_vcpu *vcpu, int ecode) { if (kvm_own_lbt(vcpu)) kvm_queue_exception(vcpu, EXCCODE_INE, 0); @@ -804,32 +823,25 @@ static int kvm_handle_lbt_disabled(struct kvm_vcpu *vcpu) return RESUME_GUEST; } -static int kvm_send_pv_ipi(struct kvm_vcpu *vcpu) +static void kvm_send_pv_ipi(struct kvm_vcpu *vcpu) { - unsigned int min, cpu, i; - unsigned long ipi_bitmap; + 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 (i = 0; i < 2; i++, min += BITS_PER_LONG) { - ipi_bitmap = kvm_read_reg(vcpu, LOONGARCH_GPR_A1 + i); - if (!ipi_bitmap) + for_each_set_bit(cpu, ipi_bitmap, BITS_PER_LONG * 2) { + dest = kvm_get_vcpu_by_cpuid(vcpu->kvm, cpu + min); + if (!dest) continue; - cpu = find_first_bit((void *)&ipi_bitmap, BITS_PER_LONG); - while (cpu < BITS_PER_LONG) { - dest = kvm_get_vcpu_by_cpuid(vcpu->kvm, cpu + min); - cpu = find_next_bit((void *)&ipi_bitmap, BITS_PER_LONG, cpu + 1); - if (!dest) - continue; - - /* Send SWI0 to dest vcpu to emulate IPI interrupt */ - kvm_queue_irq(dest, INT_SWI0); - kvm_vcpu_kick(dest); - } + /* Send SWI0 to dest vcpu to emulate IPI interrupt */ + kvm_queue_irq(dest, INT_SWI0); + kvm_vcpu_kick(dest); } - - return 0; } /* @@ -858,7 +870,7 @@ static void kvm_handle_service(struct kvm_vcpu *vcpu) kvm_write_reg(vcpu, LOONGARCH_GPR_A0, ret); } -static int kvm_handle_hypercall(struct kvm_vcpu *vcpu) +static int kvm_handle_hypercall(struct kvm_vcpu *vcpu, int ecode) { int ret; larch_inst inst; @@ -873,6 +885,28 @@ static int kvm_handle_hypercall(struct kvm_vcpu *vcpu) 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) { @@ -896,16 +930,14 @@ static int kvm_handle_hypercall(struct kvm_vcpu *vcpu) /* * LoongArch KVM callback handling for unimplemented guest exiting */ -static int kvm_fault_ni(struct kvm_vcpu *vcpu) +static int kvm_fault_ni(struct kvm_vcpu *vcpu, int ecode) { - unsigned int ecode, inst; - unsigned long estat, badv; + unsigned int inst; + unsigned long badv; /* Fetch the instruction */ inst = vcpu->arch.badi; badv = vcpu->arch.badv; - estat = vcpu->arch.host_estat; - ecode = (estat & CSR_ESTAT_EXC) >> CSR_ESTAT_EXC_SHIFT; kvm_err("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); @@ -930,5 +962,5 @@ static exit_handle_fn kvm_fault_tables[EXCCODE_INT_START] = { int kvm_handle_fault(struct kvm_vcpu *vcpu, int fault) { - return kvm_fault_tables[fault](vcpu); + return kvm_fault_tables[fault](vcpu, fault); } diff --git a/arch/loongarch/kvm/intc/eiointc.c b/arch/loongarch/kvm/intc/eiointc.c index f39929d7bf8a..a3a12af9ecbf 100644 --- a/arch/loongarch/kvm/intc/eiointc.c +++ b/arch/loongarch/kvm/intc/eiointc.c @@ -9,7 +9,8 @@ static void eiointc_set_sw_coreisr(struct loongarch_eiointc *s) { - int ipnum, cpu, irq_index, irq_mask, irq; + int ipnum, cpu, cpuid, irq; + struct kvm_vcpu *vcpu; for (irq = 0; irq < EIOINTC_IRQS; irq++) { ipnum = s->ipmap.reg_u8[irq / 32]; @@ -17,20 +18,23 @@ static void eiointc_set_sw_coreisr(struct loongarch_eiointc *s) ipnum = count_trailing_zeros(ipnum); ipnum = (ipnum >= 0 && ipnum < 4) ? ipnum : 0; } - irq_index = irq / 32; - irq_mask = BIT(irq & 0x1f); - cpu = s->coremap.reg_u8[irq]; - if (!!(s->coreisr.reg_u32[cpu][irq_index] & irq_mask)) - set_bit(irq, s->sw_coreisr[cpu][ipnum]); + cpuid = s->coremap.reg_u8[irq]; + vcpu = kvm_get_vcpu_by_cpuid(s->kvm, cpuid); + if (!vcpu) + continue; + + cpu = vcpu->vcpu_id; + if (test_bit(irq, (unsigned long *)s->coreisr.reg_u32[cpu])) + __set_bit(irq, s->sw_coreisr[cpu][ipnum]); else - clear_bit(irq, s->sw_coreisr[cpu][ipnum]); + __clear_bit(irq, s->sw_coreisr[cpu][ipnum]); } } static void eiointc_update_irq(struct loongarch_eiointc *s, int irq, int level) { - int ipnum, cpu, found, irq_index, irq_mask; + int ipnum, cpu, found; struct kvm_vcpu *vcpu; struct kvm_interrupt vcpu_irq; @@ -42,19 +46,16 @@ static void eiointc_update_irq(struct loongarch_eiointc *s, int irq, int level) cpu = s->sw_coremap[irq]; vcpu = kvm_get_vcpu(s->kvm, cpu); - irq_index = irq / 32; - irq_mask = BIT(irq & 0x1f); - if (level) { /* if not enable return false */ - if (((s->enable.reg_u32[irq_index]) & irq_mask) == 0) + if (!test_bit(irq, (unsigned long *)s->enable.reg_u32)) return; - s->coreisr.reg_u32[cpu][irq_index] |= irq_mask; + __set_bit(irq, (unsigned long *)s->coreisr.reg_u32[cpu]); found = find_first_bit(s->sw_coreisr[cpu][ipnum], EIOINTC_IRQS); - set_bit(irq, s->sw_coreisr[cpu][ipnum]); + __set_bit(irq, s->sw_coreisr[cpu][ipnum]); } else { - s->coreisr.reg_u32[cpu][irq_index] &= ~irq_mask; - clear_bit(irq, s->sw_coreisr[cpu][ipnum]); + __clear_bit(irq, (unsigned long *)s->coreisr.reg_u32[cpu]); + __clear_bit(irq, s->sw_coreisr[cpu][ipnum]); found = find_first_bit(s->sw_coreisr[cpu][ipnum], EIOINTC_IRQS); } @@ -66,20 +67,25 @@ static void eiointc_update_irq(struct loongarch_eiointc *s, int irq, int level) } static inline void eiointc_update_sw_coremap(struct loongarch_eiointc *s, - int irq, void *pvalue, u32 len, bool notify) + int irq, u64 val, u32 len, bool notify) { - int i, cpu; - u64 val = *(u64 *)pvalue; + int i, cpu, cpuid; + struct kvm_vcpu *vcpu; for (i = 0; i < len; i++) { - cpu = val & 0xff; + cpuid = val & 0xff; val = val >> 8; if (!(s->status & BIT(EIOINTC_ENABLE_CPU_ENCODE))) { - cpu = ffs(cpu) - 1; - cpu = (cpu >= 4) ? 0 : cpu; + cpuid = ffs(cpuid) - 1; + cpuid = (cpuid >= 4) ? 0 : cpuid; } + vcpu = kvm_get_vcpu_by_cpuid(s->kvm, cpuid); + if (!vcpu) + continue; + + cpu = vcpu->vcpu_id; if (s->sw_coremap[irq + i] == cpu) continue; @@ -99,159 +105,14 @@ void eiointc_set_irq(struct loongarch_eiointc *s, int irq, int level) unsigned long flags; unsigned long *isr = (unsigned long *)s->isr.reg_u8; - level ? set_bit(irq, isr) : clear_bit(irq, isr); spin_lock_irqsave(&s->lock, flags); + level ? __set_bit(irq, isr) : __clear_bit(irq, isr); eiointc_update_irq(s, irq, level); spin_unlock_irqrestore(&s->lock, flags); } -static inline void eiointc_enable_irq(struct kvm_vcpu *vcpu, - struct loongarch_eiointc *s, int index, u8 mask, int level) -{ - u8 val; - int irq; - - val = mask & s->isr.reg_u8[index]; - irq = ffs(val); - while (irq != 0) { - /* - * enable bit change from 0 to 1, - * need to update irq by pending bits - */ - eiointc_update_irq(s, irq - 1 + index * 8, level); - val &= ~BIT(irq - 1); - irq = ffs(val); - } -} - -static int loongarch_eiointc_readb(struct kvm_vcpu *vcpu, struct loongarch_eiointc *s, - gpa_t addr, int len, void *val) -{ - int index, ret = 0; - u8 data = 0; - gpa_t offset; - - offset = addr - EIOINTC_BASE; - switch (offset) { - case EIOINTC_NODETYPE_START ... EIOINTC_NODETYPE_END: - index = offset - EIOINTC_NODETYPE_START; - data = s->nodetype.reg_u8[index]; - break; - case EIOINTC_IPMAP_START ... EIOINTC_IPMAP_END: - index = offset - EIOINTC_IPMAP_START; - data = s->ipmap.reg_u8[index]; - break; - case EIOINTC_ENABLE_START ... EIOINTC_ENABLE_END: - index = offset - EIOINTC_ENABLE_START; - data = s->enable.reg_u8[index]; - break; - case EIOINTC_BOUNCE_START ... EIOINTC_BOUNCE_END: - index = offset - EIOINTC_BOUNCE_START; - data = s->bounce.reg_u8[index]; - break; - case EIOINTC_COREISR_START ... EIOINTC_COREISR_END: - index = offset - EIOINTC_COREISR_START; - data = s->coreisr.reg_u8[vcpu->vcpu_id][index]; - break; - case EIOINTC_COREMAP_START ... EIOINTC_COREMAP_END: - index = offset - EIOINTC_COREMAP_START; - data = s->coremap.reg_u8[index]; - break; - default: - ret = -EINVAL; - break; - } - *(u8 *)val = data; - - return ret; -} - -static int loongarch_eiointc_readw(struct kvm_vcpu *vcpu, struct loongarch_eiointc *s, - gpa_t addr, int len, void *val) -{ - int index, ret = 0; - u16 data = 0; - gpa_t offset; - - offset = addr - EIOINTC_BASE; - switch (offset) { - case EIOINTC_NODETYPE_START ... EIOINTC_NODETYPE_END: - index = (offset - EIOINTC_NODETYPE_START) >> 1; - data = s->nodetype.reg_u16[index]; - break; - case EIOINTC_IPMAP_START ... EIOINTC_IPMAP_END: - index = (offset - EIOINTC_IPMAP_START) >> 1; - data = s->ipmap.reg_u16[index]; - break; - case EIOINTC_ENABLE_START ... EIOINTC_ENABLE_END: - index = (offset - EIOINTC_ENABLE_START) >> 1; - data = s->enable.reg_u16[index]; - break; - case EIOINTC_BOUNCE_START ... EIOINTC_BOUNCE_END: - index = (offset - EIOINTC_BOUNCE_START) >> 1; - data = s->bounce.reg_u16[index]; - break; - case EIOINTC_COREISR_START ... EIOINTC_COREISR_END: - index = (offset - EIOINTC_COREISR_START) >> 1; - data = s->coreisr.reg_u16[vcpu->vcpu_id][index]; - break; - case EIOINTC_COREMAP_START ... EIOINTC_COREMAP_END: - index = (offset - EIOINTC_COREMAP_START) >> 1; - data = s->coremap.reg_u16[index]; - break; - default: - ret = -EINVAL; - break; - } - *(u16 *)val = data; - - return ret; -} - -static int loongarch_eiointc_readl(struct kvm_vcpu *vcpu, struct loongarch_eiointc *s, - gpa_t addr, int len, void *val) -{ - int index, ret = 0; - u32 data = 0; - gpa_t offset; - - offset = addr - EIOINTC_BASE; - switch (offset) { - case EIOINTC_NODETYPE_START ... EIOINTC_NODETYPE_END: - index = (offset - EIOINTC_NODETYPE_START) >> 2; - data = s->nodetype.reg_u32[index]; - break; - case EIOINTC_IPMAP_START ... EIOINTC_IPMAP_END: - index = (offset - EIOINTC_IPMAP_START) >> 2; - data = s->ipmap.reg_u32[index]; - break; - case EIOINTC_ENABLE_START ... EIOINTC_ENABLE_END: - index = (offset - EIOINTC_ENABLE_START) >> 2; - data = s->enable.reg_u32[index]; - break; - case EIOINTC_BOUNCE_START ... EIOINTC_BOUNCE_END: - index = (offset - EIOINTC_BOUNCE_START) >> 2; - data = s->bounce.reg_u32[index]; - break; - case EIOINTC_COREISR_START ... EIOINTC_COREISR_END: - index = (offset - EIOINTC_COREISR_START) >> 2; - data = s->coreisr.reg_u32[vcpu->vcpu_id][index]; - break; - case EIOINTC_COREMAP_START ... EIOINTC_COREMAP_END: - index = (offset - EIOINTC_COREMAP_START) >> 2; - data = s->coremap.reg_u32[index]; - break; - default: - ret = -EINVAL; - break; - } - *(u32 *)val = data; - - return ret; -} - -static int loongarch_eiointc_readq(struct kvm_vcpu *vcpu, struct loongarch_eiointc *s, - gpa_t addr, int len, void *val) +static int loongarch_eiointc_read(struct kvm_vcpu *vcpu, struct loongarch_eiointc *s, + gpa_t addr, unsigned long *val) { int index, ret = 0; u64 data = 0; @@ -287,7 +148,7 @@ static int loongarch_eiointc_readq(struct kvm_vcpu *vcpu, struct loongarch_eioin ret = -EINVAL; break; } - *(u64 *)val = data; + *val = data; return ret; } @@ -297,7 +158,7 @@ static int kvm_eiointc_read(struct kvm_vcpu *vcpu, gpa_t addr, int len, void *val) { int ret = -EINVAL; - unsigned long flags; + unsigned long flags, data, offset; struct loongarch_eiointc *eiointc = vcpu->kvm->arch.eiointc; if (!eiointc) { @@ -305,358 +166,120 @@ static int kvm_eiointc_read(struct kvm_vcpu *vcpu, return -EINVAL; } - vcpu->kvm->stat.eiointc_read_exits++; + if (addr & (len - 1)) { + kvm_err("%s: eiointc not aligned addr %llx len %d\n", __func__, addr, len); + return -EINVAL; + } + + offset = addr & 0x7; + addr -= offset; + vcpu->stat.eiointc_read_exits++; spin_lock_irqsave(&eiointc->lock, flags); + ret = loongarch_eiointc_read(vcpu, eiointc, addr, &data); + spin_unlock_irqrestore(&eiointc->lock, flags); + if (ret) + return ret; + + data = data >> (offset * 8); switch (len) { case 1: - ret = loongarch_eiointc_readb(vcpu, eiointc, addr, len, val); + *(long *)val = (s8)data; break; case 2: - ret = loongarch_eiointc_readw(vcpu, eiointc, addr, len, val); + *(long *)val = (s16)data; break; case 4: - ret = loongarch_eiointc_readl(vcpu, eiointc, addr, len, val); - break; - case 8: - ret = loongarch_eiointc_readq(vcpu, eiointc, addr, len, val); + *(long *)val = (s32)data; break; default: - WARN_ONCE(1, "%s: Abnormal address access: addr 0x%llx, size %d\n", - __func__, addr, len); - } - spin_unlock_irqrestore(&eiointc->lock, flags); - - return ret; -} - -static int loongarch_eiointc_writeb(struct kvm_vcpu *vcpu, - struct loongarch_eiointc *s, - gpa_t addr, int len, const void *val) -{ - int index, irq, bits, ret = 0; - u8 cpu; - u8 data, old_data; - u8 coreisr, old_coreisr; - gpa_t offset; - - data = *(u8 *)val; - offset = addr - EIOINTC_BASE; - - switch (offset) { - case EIOINTC_NODETYPE_START ... EIOINTC_NODETYPE_END: - index = (offset - EIOINTC_NODETYPE_START); - s->nodetype.reg_u8[index] = data; - break; - case EIOINTC_IPMAP_START ... EIOINTC_IPMAP_END: - /* - * ipmap cannot be set at runtime, can be set only at the beginning - * of irqchip driver, need not update upper irq level - */ - index = (offset - EIOINTC_IPMAP_START); - s->ipmap.reg_u8[index] = data; - break; - case EIOINTC_ENABLE_START ... EIOINTC_ENABLE_END: - index = (offset - EIOINTC_ENABLE_START); - old_data = s->enable.reg_u8[index]; - s->enable.reg_u8[index] = data; - /* - * 1: enable irq. - * update irq when isr is set. - */ - data = s->enable.reg_u8[index] & ~old_data & s->isr.reg_u8[index]; - eiointc_enable_irq(vcpu, s, index, data, 1); - /* - * 0: disable irq. - * update irq when isr is set. - */ - data = ~s->enable.reg_u8[index] & old_data & s->isr.reg_u8[index]; - eiointc_enable_irq(vcpu, s, index, data, 0); - break; - case EIOINTC_BOUNCE_START ... EIOINTC_BOUNCE_END: - /* do not emulate hw bounced irq routing */ - index = offset - EIOINTC_BOUNCE_START; - s->bounce.reg_u8[index] = data; - break; - case EIOINTC_COREISR_START ... EIOINTC_COREISR_END: - index = (offset - EIOINTC_COREISR_START); - /* use attrs to get current cpu index */ - cpu = vcpu->vcpu_id; - coreisr = data; - old_coreisr = s->coreisr.reg_u8[cpu][index]; - /* write 1 to clear interrupt */ - s->coreisr.reg_u8[cpu][index] = old_coreisr & ~coreisr; - coreisr &= old_coreisr; - bits = sizeof(data) * 8; - irq = find_first_bit((void *)&coreisr, bits); - while (irq < bits) { - eiointc_update_irq(s, irq + index * bits, 0); - bitmap_clear((void *)&coreisr, irq, 1); - irq = find_first_bit((void *)&coreisr, bits); - } - break; - case EIOINTC_COREMAP_START ... EIOINTC_COREMAP_END: - irq = offset - EIOINTC_COREMAP_START; - index = irq; - s->coremap.reg_u8[index] = data; - eiointc_update_sw_coremap(s, irq, (void *)&data, sizeof(data), true); - break; - default: - ret = -EINVAL; + *(long *)val = (long)data; break; } - return ret; -} - -static int loongarch_eiointc_writew(struct kvm_vcpu *vcpu, - struct loongarch_eiointc *s, - gpa_t addr, int len, const void *val) -{ - int i, index, irq, bits, ret = 0; - u8 cpu; - u16 data, old_data; - u16 coreisr, old_coreisr; - gpa_t offset; - - data = *(u16 *)val; - offset = addr - EIOINTC_BASE; - - switch (offset) { - case EIOINTC_NODETYPE_START ... EIOINTC_NODETYPE_END: - index = (offset - EIOINTC_NODETYPE_START) >> 1; - s->nodetype.reg_u16[index] = data; - break; - case EIOINTC_IPMAP_START ... EIOINTC_IPMAP_END: - /* - * ipmap cannot be set at runtime, can be set only at the beginning - * of irqchip driver, need not update upper irq level - */ - index = (offset - EIOINTC_IPMAP_START) >> 1; - s->ipmap.reg_u16[index] = data; - break; - case EIOINTC_ENABLE_START ... EIOINTC_ENABLE_END: - index = (offset - EIOINTC_ENABLE_START) >> 1; - old_data = s->enable.reg_u32[index]; - s->enable.reg_u16[index] = data; - /* - * 1: enable irq. - * update irq when isr is set. - */ - data = s->enable.reg_u16[index] & ~old_data & s->isr.reg_u16[index]; - index = index << 1; - for (i = 0; i < sizeof(data); i++) { - u8 mask = (data >> (i * 8)) & 0xff; - eiointc_enable_irq(vcpu, s, index + i, mask, 1); - } - /* - * 0: disable irq. - * update irq when isr is set. - */ - data = ~s->enable.reg_u16[index] & old_data & s->isr.reg_u16[index]; - for (i = 0; i < sizeof(data); i++) { - u8 mask = (data >> (i * 8)) & 0xff; - eiointc_enable_irq(vcpu, s, index, mask, 0); - } - break; - case EIOINTC_BOUNCE_START ... EIOINTC_BOUNCE_END: - /* do not emulate hw bounced irq routing */ - index = (offset - EIOINTC_BOUNCE_START) >> 1; - s->bounce.reg_u16[index] = data; - break; - case EIOINTC_COREISR_START ... EIOINTC_COREISR_END: - index = (offset - EIOINTC_COREISR_START) >> 1; - /* use attrs to get current cpu index */ - cpu = vcpu->vcpu_id; - coreisr = data; - old_coreisr = s->coreisr.reg_u16[cpu][index]; - /* write 1 to clear interrupt */ - s->coreisr.reg_u16[cpu][index] = old_coreisr & ~coreisr; - coreisr &= old_coreisr; - bits = sizeof(data) * 8; - irq = find_first_bit((void *)&coreisr, bits); - while (irq < bits) { - eiointc_update_irq(s, irq + index * bits, 0); - bitmap_clear((void *)&coreisr, irq, 1); - irq = find_first_bit((void *)&coreisr, bits); - } - break; - case EIOINTC_COREMAP_START ... EIOINTC_COREMAP_END: - irq = offset - EIOINTC_COREMAP_START; - index = irq >> 1; - s->coremap.reg_u16[index] = data; - eiointc_update_sw_coremap(s, irq, (void *)&data, sizeof(data), true); - break; - default: - ret = -EINVAL; - break; - } - - return ret; + return 0; } -static int loongarch_eiointc_writel(struct kvm_vcpu *vcpu, +static int loongarch_eiointc_write(struct kvm_vcpu *vcpu, struct loongarch_eiointc *s, - gpa_t addr, int len, const void *val) + gpa_t addr, u64 value, u64 field_mask) { - int i, index, irq, bits, ret = 0; + int index, irq, ret = 0; u8 cpu; - u32 data, old_data; - u32 coreisr, old_coreisr; + u64 data, old, mask; gpa_t offset; - data = *(u32 *)val; - offset = addr - EIOINTC_BASE; + offset = addr & 7; + mask = field_mask << (offset * 8); + data = (value & field_mask) << (offset * 8); - switch (offset) { - case EIOINTC_NODETYPE_START ... EIOINTC_NODETYPE_END: - index = (offset - EIOINTC_NODETYPE_START) >> 2; - s->nodetype.reg_u32[index] = data; - break; - case EIOINTC_IPMAP_START ... EIOINTC_IPMAP_END: - /* - * ipmap cannot be set at runtime, can be set only at the beginning - * of irqchip driver, need not update upper irq level - */ - index = (offset - EIOINTC_IPMAP_START) >> 2; - s->ipmap.reg_u32[index] = data; - break; - case EIOINTC_ENABLE_START ... EIOINTC_ENABLE_END: - index = (offset - EIOINTC_ENABLE_START) >> 2; - old_data = s->enable.reg_u32[index]; - s->enable.reg_u32[index] = data; - /* - * 1: enable irq. - * update irq when isr is set. - */ - data = s->enable.reg_u32[index] & ~old_data & s->isr.reg_u32[index]; - index = index << 2; - for (i = 0; i < sizeof(data); i++) { - u8 mask = (data >> (i * 8)) & 0xff; - eiointc_enable_irq(vcpu, s, index + i, mask, 1); - } - /* - * 0: disable irq. - * update irq when isr is set. - */ - data = ~s->enable.reg_u32[index] & old_data & s->isr.reg_u32[index]; - for (i = 0; i < sizeof(data); i++) { - u8 mask = (data >> (i * 8)) & 0xff; - eiointc_enable_irq(vcpu, s, index, mask, 0); - } - break; - case EIOINTC_BOUNCE_START ... EIOINTC_BOUNCE_END: - /* do not emulate hw bounced irq routing */ - index = (offset - EIOINTC_BOUNCE_START) >> 2; - s->bounce.reg_u32[index] = data; - break; - case EIOINTC_COREISR_START ... EIOINTC_COREISR_END: - index = (offset - EIOINTC_COREISR_START) >> 2; - /* use attrs to get current cpu index */ - cpu = vcpu->vcpu_id; - coreisr = data; - old_coreisr = s->coreisr.reg_u32[cpu][index]; - /* write 1 to clear interrupt */ - s->coreisr.reg_u32[cpu][index] = old_coreisr & ~coreisr; - coreisr &= old_coreisr; - bits = sizeof(data) * 8; - irq = find_first_bit((void *)&coreisr, bits); - while (irq < bits) { - eiointc_update_irq(s, irq + index * bits, 0); - bitmap_clear((void *)&coreisr, irq, 1); - irq = find_first_bit((void *)&coreisr, bits); - } - break; - case EIOINTC_COREMAP_START ... EIOINTC_COREMAP_END: - irq = offset - EIOINTC_COREMAP_START; - index = irq >> 2; - s->coremap.reg_u32[index] = data; - eiointc_update_sw_coremap(s, irq, (void *)&data, sizeof(data), true); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -static int loongarch_eiointc_writeq(struct kvm_vcpu *vcpu, - struct loongarch_eiointc *s, - gpa_t addr, int len, const void *val) -{ - int i, index, irq, bits, ret = 0; - u8 cpu; - u64 data, old_data; - u64 coreisr, old_coreisr; - gpa_t offset; - - data = *(u64 *)val; + addr -= offset; offset = addr - EIOINTC_BASE; switch (offset) { case EIOINTC_NODETYPE_START ... EIOINTC_NODETYPE_END: index = (offset - EIOINTC_NODETYPE_START) >> 3; - s->nodetype.reg_u64[index] = data; + old = s->nodetype.reg_u64[index]; + s->nodetype.reg_u64[index] = (old & ~mask) | data; break; case EIOINTC_IPMAP_START ... EIOINTC_IPMAP_END: /* * ipmap cannot be set at runtime, can be set only at the beginning * of irqchip driver, need not update upper irq level */ - index = (offset - EIOINTC_IPMAP_START) >> 3; - s->ipmap.reg_u64 = data; + old = s->ipmap.reg_u64; + s->ipmap.reg_u64 = (old & ~mask) | data; break; case EIOINTC_ENABLE_START ... EIOINTC_ENABLE_END: index = (offset - EIOINTC_ENABLE_START) >> 3; - old_data = s->enable.reg_u64[index]; - s->enable.reg_u64[index] = data; + old = s->enable.reg_u64[index]; + s->enable.reg_u64[index] = (old & ~mask) | data; /* * 1: enable irq. * update irq when isr is set. */ - data = s->enable.reg_u64[index] & ~old_data & s->isr.reg_u64[index]; - index = index << 3; - for (i = 0; i < sizeof(data); i++) { - u8 mask = (data >> (i * 8)) & 0xff; - eiointc_enable_irq(vcpu, s, index + i, mask, 1); + data = s->enable.reg_u64[index] & ~old & s->isr.reg_u64[index]; + while (data) { + irq = __ffs(data); + eiointc_update_irq(s, irq + index * 64, 1); + data &= ~BIT_ULL(irq); } /* * 0: disable irq. * update irq when isr is set. */ - data = ~s->enable.reg_u64[index] & old_data & s->isr.reg_u64[index]; - for (i = 0; i < sizeof(data); i++) { - u8 mask = (data >> (i * 8)) & 0xff; - eiointc_enable_irq(vcpu, s, index, mask, 0); + data = ~s->enable.reg_u64[index] & old & s->isr.reg_u64[index]; + while (data) { + irq = __ffs(data); + eiointc_update_irq(s, irq + index * 64, 0); + data &= ~BIT_ULL(irq); } break; case EIOINTC_BOUNCE_START ... EIOINTC_BOUNCE_END: /* do not emulate hw bounced irq routing */ index = (offset - EIOINTC_BOUNCE_START) >> 3; - s->bounce.reg_u64[index] = data; + old = s->bounce.reg_u64[index]; + s->bounce.reg_u64[index] = (old & ~mask) | data; break; case EIOINTC_COREISR_START ... EIOINTC_COREISR_END: index = (offset - EIOINTC_COREISR_START) >> 3; /* use attrs to get current cpu index */ cpu = vcpu->vcpu_id; - coreisr = data; - old_coreisr = s->coreisr.reg_u64[cpu][index]; + old = s->coreisr.reg_u64[cpu][index]; /* write 1 to clear interrupt */ - s->coreisr.reg_u64[cpu][index] = old_coreisr & ~coreisr; - coreisr &= old_coreisr; - bits = sizeof(data) * 8; - irq = find_first_bit((void *)&coreisr, bits); - while (irq < bits) { - eiointc_update_irq(s, irq + index * bits, 0); - bitmap_clear((void *)&coreisr, irq, 1); - irq = find_first_bit((void *)&coreisr, bits); + s->coreisr.reg_u64[cpu][index] = old & ~data; + data &= old; + while (data) { + irq = __ffs(data); + eiointc_update_irq(s, irq + index * 64, 0); + data &= ~BIT_ULL(irq); } break; case EIOINTC_COREMAP_START ... EIOINTC_COREMAP_END: - irq = offset - EIOINTC_COREMAP_START; - index = irq >> 3; - s->coremap.reg_u64[index] = data; - eiointc_update_sw_coremap(s, irq, (void *)&data, sizeof(data), true); + index = (offset - EIOINTC_COREMAP_START) >> 3; + old = s->coremap.reg_u64[index]; + s->coremap.reg_u64[index] = (old & ~mask) | data; + data = s->coremap.reg_u64[index]; + eiointc_update_sw_coremap(s, index * 8, data, sizeof(data), true); break; default: ret = -EINVAL; @@ -671,7 +294,7 @@ static int kvm_eiointc_write(struct kvm_vcpu *vcpu, gpa_t addr, int len, const void *val) { int ret = -EINVAL; - unsigned long flags; + unsigned long flags, value; struct loongarch_eiointc *eiointc = vcpu->kvm->arch.eiointc; if (!eiointc) { @@ -679,24 +302,30 @@ static int kvm_eiointc_write(struct kvm_vcpu *vcpu, return -EINVAL; } - vcpu->kvm->stat.eiointc_write_exits++; + if (addr & (len - 1)) { + kvm_err("%s: eiointc not aligned addr %llx len %d\n", __func__, addr, len); + return -EINVAL; + } + + vcpu->stat.eiointc_write_exits++; spin_lock_irqsave(&eiointc->lock, flags); switch (len) { case 1: - ret = loongarch_eiointc_writeb(vcpu, eiointc, addr, len, val); + value = *(unsigned char *)val; + ret = loongarch_eiointc_write(vcpu, eiointc, addr, value, 0xFF); break; case 2: - ret = loongarch_eiointc_writew(vcpu, eiointc, addr, len, val); + value = *(unsigned short *)val; + ret = loongarch_eiointc_write(vcpu, eiointc, addr, value, USHRT_MAX); break; case 4: - ret = loongarch_eiointc_writel(vcpu, eiointc, addr, len, val); - break; - case 8: - ret = loongarch_eiointc_writeq(vcpu, eiointc, addr, len, val); + value = *(unsigned int *)val; + ret = loongarch_eiointc_write(vcpu, eiointc, addr, value, UINT_MAX); break; default: - WARN_ONCE(1, "%s: Abnormal address access: addr 0x%llx, size %d\n", - __func__, addr, len); + value = *(unsigned long *)val; + ret = loongarch_eiointc_write(vcpu, eiointc, addr, value, ULONG_MAX); + break; } spin_unlock_irqrestore(&eiointc->lock, flags); @@ -787,7 +416,7 @@ static int kvm_eiointc_ctrl_access(struct kvm_device *dev, int ret = 0; unsigned long flags; unsigned long type = (unsigned long)attr->attr; - u32 i, start_irq; + u32 i, start_irq, val; void __user *data; struct loongarch_eiointc *s = dev->kvm->arch.eiointc; @@ -795,8 +424,14 @@ static int kvm_eiointc_ctrl_access(struct kvm_device *dev, spin_lock_irqsave(&s->lock, flags); switch (type) { case KVM_DEV_LOONGARCH_EXTIOI_CTRL_INIT_NUM_CPU: - if (copy_from_user(&s->num_cpu, data, 4)) + if (copy_from_user(&val, data, 4)) ret = -EFAULT; + else { + if (val >= EIOINTC_ROUTE_MAX_VCPUS) + ret = -EINVAL; + else + s->num_cpu = val; + } break; case KVM_DEV_LOONGARCH_EXTIOI_CTRL_INIT_FEATURE: if (copy_from_user(&s->features, data, 4)) @@ -809,7 +444,7 @@ static int kvm_eiointc_ctrl_access(struct kvm_device *dev, for (i = 0; i < (EIOINTC_IRQS / 4); i++) { start_irq = i * 4; eiointc_update_sw_coremap(s, start_irq, - (void *)&s->coremap.reg_u32[i], sizeof(u32), false); + s->coremap.reg_u32[i], sizeof(u32), false); } break; default: @@ -824,7 +459,7 @@ static int kvm_eiointc_regs_access(struct kvm_device *dev, struct kvm_device_attr *attr, bool is_write) { - int addr, cpuid, offset, ret = 0; + int addr, cpu, offset, ret = 0; unsigned long flags; void *p = NULL; void __user *data; @@ -832,7 +467,7 @@ static int kvm_eiointc_regs_access(struct kvm_device *dev, s = dev->kvm->arch.eiointc; addr = attr->attr; - cpuid = addr >> 16; + cpu = addr >> 16; addr &= 0xffff; data = (void __user *)attr->addr; switch (addr) { @@ -857,8 +492,11 @@ static int kvm_eiointc_regs_access(struct kvm_device *dev, p = &s->isr.reg_u32[offset]; break; case EIOINTC_COREISR_START ... EIOINTC_COREISR_END: + if (cpu >= s->num_cpu) + return -EINVAL; + offset = (addr - EIOINTC_COREISR_START) / 4; - p = &s->coreisr.reg_u32[cpuid][offset]; + p = &s->coreisr.reg_u32[cpu][offset]; break; case EIOINTC_COREMAP_START ... EIOINTC_COREMAP_END: offset = (addr - EIOINTC_COREMAP_START) / 4; @@ -899,9 +537,15 @@ static int kvm_eiointc_sw_status_access(struct kvm_device *dev, data = (void __user *)attr->addr; switch (addr) { case KVM_DEV_LOONGARCH_EXTIOI_SW_STATUS_NUM_CPU: + if (is_write) + return ret; + p = &s->num_cpu; break; case KVM_DEV_LOONGARCH_EXTIOI_SW_STATUS_FEATURE: + if (is_write) + return ret; + p = &s->features; break; case KVM_DEV_LOONGARCH_EXTIOI_SW_STATUS_STATE: @@ -956,7 +600,7 @@ static int kvm_eiointc_create(struct kvm_device *dev, u32 type) { int ret; struct loongarch_eiointc *s; - struct kvm_io_device *device, *device1; + struct kvm_io_device *device; struct kvm *kvm = dev->kvm; /* eiointc has been created */ @@ -984,10 +628,10 @@ static int kvm_eiointc_create(struct kvm_device *dev, u32 type) return ret; } - device1 = &s->device_vext; - kvm_iodevice_init(device1, &kvm_eiointc_virt_ops); + device = &s->device_vext; + kvm_iodevice_init(device, &kvm_eiointc_virt_ops); ret = kvm_io_bus_register_dev(kvm, KVM_IOCSR_BUS, - EIOINTC_VIRT_BASE, EIOINTC_VIRT_SIZE, device1); + EIOINTC_VIRT_BASE, EIOINTC_VIRT_SIZE, device); if (ret < 0) { kvm_io_bus_unregister_dev(kvm, KVM_IOCSR_BUS, &s->device); kfree(s); diff --git a/arch/loongarch/kvm/intc/ipi.c b/arch/loongarch/kvm/intc/ipi.c index 93f4acd44523..e658d5b37c04 100644 --- a/arch/loongarch/kvm/intc/ipi.c +++ b/arch/loongarch/kvm/intc/ipi.c @@ -111,7 +111,7 @@ static int send_ipi_data(struct kvm_vcpu *vcpu, gpa_t addr, uint64_t data) ret = kvm_io_bus_read(vcpu, KVM_IOCSR_BUS, addr, sizeof(val), &val); srcu_read_unlock(&vcpu->kvm->srcu, idx); if (unlikely(ret)) { - kvm_err("%s: : read date from addr %llx failed\n", __func__, addr); + kvm_err("%s: : read data from addr %llx failed\n", __func__, addr); return ret; } /* Construct the mask by scanning the bit 27-30 */ @@ -127,7 +127,7 @@ static int send_ipi_data(struct kvm_vcpu *vcpu, gpa_t addr, uint64_t data) ret = kvm_io_bus_write(vcpu, KVM_IOCSR_BUS, addr, sizeof(val), &val); srcu_read_unlock(&vcpu->kvm->srcu, idx); if (unlikely(ret)) - kvm_err("%s: : write date to addr %llx failed\n", __func__, addr); + kvm_err("%s: : write data to addr %llx failed\n", __func__, addr); return ret; } @@ -268,36 +268,16 @@ static int kvm_ipi_read(struct kvm_vcpu *vcpu, struct kvm_io_device *dev, gpa_t addr, int len, void *val) { - int ret; - struct loongarch_ipi *ipi; - - ipi = vcpu->kvm->arch.ipi; - if (!ipi) { - kvm_err("%s: ipi irqchip not valid!\n", __func__); - return -EINVAL; - } - ipi->kvm->stat.ipi_read_exits++; - ret = loongarch_ipi_readl(vcpu, addr, len, val); - - return ret; + vcpu->stat.ipi_read_exits++; + return loongarch_ipi_readl(vcpu, addr, len, val); } static int kvm_ipi_write(struct kvm_vcpu *vcpu, struct kvm_io_device *dev, gpa_t addr, int len, const void *val) { - int ret; - struct loongarch_ipi *ipi; - - ipi = vcpu->kvm->arch.ipi; - if (!ipi) { - kvm_err("%s: ipi irqchip not valid!\n", __func__); - return -EINVAL; - } - ipi->kvm->stat.ipi_write_exits++; - ret = loongarch_ipi_writel(vcpu, addr, len, val); - - return ret; + vcpu->stat.ipi_write_exits++; + return loongarch_ipi_writel(vcpu, addr, len, val); } static const struct kvm_io_device_ops kvm_ipi_ops = { diff --git a/arch/loongarch/kvm/intc/pch_pic.c b/arch/loongarch/kvm/intc/pch_pic.c index 08fce845f668..6f00ffe05c54 100644 --- a/arch/loongarch/kvm/intc/pch_pic.c +++ b/arch/loongarch/kvm/intc/pch_pic.c @@ -196,7 +196,7 @@ static int kvm_pch_pic_read(struct kvm_vcpu *vcpu, } /* statistics of pch pic reading */ - vcpu->kvm->stat.pch_pic_read_exits++; + vcpu->stat.pch_pic_read_exits++; ret = loongarch_pch_pic_read(s, addr, len, val); return ret; @@ -303,7 +303,7 @@ static int kvm_pch_pic_write(struct kvm_vcpu *vcpu, } /* statistics of pch pic writing */ - vcpu->kvm->stat.pch_pic_write_exits++; + vcpu->stat.pch_pic_write_exits++; ret = loongarch_pch_pic_write(s, addr, len, val); return ret; diff --git a/arch/loongarch/kvm/interrupt.c b/arch/loongarch/kvm/interrupt.c index 4c3f22de4b40..8462083f0301 100644 --- a/arch/loongarch/kvm/interrupt.c +++ b/arch/loongarch/kvm/interrupt.c @@ -83,28 +83,11 @@ void kvm_deliver_intr(struct kvm_vcpu *vcpu) unsigned long *pending = &vcpu->arch.irq_pending; unsigned long *pending_clr = &vcpu->arch.irq_clear; - if (!(*pending) && !(*pending_clr)) - return; - - if (*pending_clr) { - priority = __ffs(*pending_clr); - while (priority <= INT_IPI) { - kvm_irq_clear(vcpu, priority); - priority = find_next_bit(pending_clr, - BITS_PER_BYTE * sizeof(*pending_clr), - priority + 1); - } - } + for_each_set_bit(priority, pending_clr, INT_IPI + 1) + kvm_irq_clear(vcpu, priority); - if (*pending) { - priority = __ffs(*pending); - while (priority <= INT_IPI) { - kvm_irq_deliver(vcpu, priority); - priority = find_next_bit(pending, - BITS_PER_BYTE * sizeof(*pending), - priority + 1); - } - } + for_each_set_bit(priority, pending, INT_IPI + 1) + kvm_irq_deliver(vcpu, priority); } int kvm_pending_timer(struct kvm_vcpu *vcpu) diff --git a/arch/loongarch/kvm/main.c b/arch/loongarch/kvm/main.c index 396fed2665a5..80ea63d465b8 100644 --- a/arch/loongarch/kvm/main.c +++ b/arch/loongarch/kvm/main.c @@ -245,6 +245,24 @@ void kvm_check_vpid(struct kvm_vcpu *vcpu) trace_kvm_vpid_change(vcpu, vcpu->arch.vpid); vcpu->cpu = cpu; kvm_clear_request(KVM_REQ_TLB_FLUSH_GPA, vcpu); + + /* + * LLBCTL is a separated guest CSR register from host, a general + * exception ERET instruction clears the host LLBCTL register in + * host mode, and clears the guest LLBCTL register in guest mode. + * ERET in tlb refill exception does not clear LLBCTL register. + * + * When secondary mmu mapping is changed, guest OS does not know + * even if the content is changed after mapping is changed. + * + * Here clear WCLLB of the guest LLBCTL register when mapping is + * changed. Otherwise, if mmu mapping is changed while guest is + * executing LL/SC pair, LL loads with the old address and set + * the LLBCTL flag, SC checks the LLBCTL flag and will store the + * new address successfully since LLBCTL_WCLLB is on, even if + * memory with new address is changed on other VCPUs. + */ + set_gcsr_llbctl(CSR_LLBCTL_WCLLB); } /* Restore GSTAT(0x50).vpid */ @@ -278,16 +296,16 @@ int kvm_arch_enable_virtualization_cpu(void) /* * Enable virtualization features granting guest direct control of * certain features: - * GCI=2: Trap on init or unimplement cache instruction. + * GCI=2: Trap on init or unimplemented cache instruction. * TORU=0: Trap on Root Unimplement. * CACTRL=1: Root control cache. - * TOP=0: Trap on Previlege. + * TOP=0: Trap on Privilege. * TOE=0: Trap on Exception. * TIT=0: Trap on Timer. */ - if (env & CSR_GCFG_GCIP_ALL) + if (env & CSR_GCFG_GCIP_SECURE) gcfg |= CSR_GCFG_GCI_SECURE; - if (env & CSR_GCFG_MATC_ROOT) + if (env & CSR_GCFG_MATP_ROOT) gcfg |= CSR_GCFG_MATC_ROOT; write_csr_gcfg(gcfg); @@ -299,6 +317,13 @@ int kvm_arch_enable_virtualization_cpu(void) kvm_debug("GCFG:%lx GSTAT:%lx GINTC:%lx GTLBC:%lx", read_csr_gcfg(), read_csr_gstat(), read_csr_gintc(), read_csr_gtlbc()); + /* + * HW Guest CSR registers are lost after CPU suspend and resume. + * Clear last_vcpu so that Guest CSR registers forced to reload + * from vCPU SW state. + */ + this_cpu_ptr(vmcs)->last_vcpu = NULL; + return 0; } @@ -369,6 +394,7 @@ static int kvm_loongarch_env_init(void) } kvm_init_gcsr_flag(); + kvm_register_perf_callbacks(NULL); /* Register LoongArch IPI interrupt controller interface. */ ret = kvm_loongarch_register_ipi_device(); @@ -400,6 +426,8 @@ static void kvm_loongarch_env_exit(void) } kfree(kvm_loongarch_ops); } + + kvm_unregister_perf_callbacks(); } static int kvm_loongarch_init(void) diff --git a/arch/loongarch/kvm/mmu.c b/arch/loongarch/kvm/mmu.c index 4d203294767c..ed956c5cf2cc 100644 --- a/arch/loongarch/kvm/mmu.c +++ b/arch/loongarch/kvm/mmu.c @@ -912,7 +912,7 @@ out: return err; } -int kvm_handle_mm_fault(struct kvm_vcpu *vcpu, unsigned long gpa, bool write) +int kvm_handle_mm_fault(struct kvm_vcpu *vcpu, unsigned long gpa, bool write, int ecode) { int ret; @@ -921,8 +921,17 @@ int kvm_handle_mm_fault(struct kvm_vcpu *vcpu, unsigned long gpa, bool write) return ret; /* Invalidate this entry in the TLB */ - vcpu->arch.flush_gpa = gpa; - kvm_make_request(KVM_REQ_TLB_FLUSH_GPA, vcpu); + if (!cpu_has_ptw || (ecode == EXCCODE_TLBM)) { + /* + * With HW PTW, invalid TLB is not added when page fault. But + * for EXCCODE_TLBM exception, stale TLB may exist because of + * the last read access. + * + * With SW PTW, invalid TLB is added in TLB refill exception. + */ + vcpu->arch.flush_gpa = gpa; + kvm_make_request(KVM_REQ_TLB_FLUSH_GPA, vcpu); + } return 0; } diff --git a/arch/loongarch/kvm/switch.S b/arch/loongarch/kvm/switch.S index 0c292f818492..f1768b7a6194 100644 --- a/arch/loongarch/kvm/switch.S +++ b/arch/loongarch/kvm/switch.S @@ -60,16 +60,8 @@ ld.d t0, a2, KVM_ARCH_GPC csrwr t0, LOONGARCH_CSR_ERA - /* Save host PGDL */ - csrrd t0, LOONGARCH_CSR_PGDL - st.d t0, a2, KVM_ARCH_HPGD - - /* Switch to kvm */ - ld.d t1, a2, KVM_VCPU_KVM - KVM_VCPU_ARCH - - /* Load guest PGDL */ - li.w t0, KVM_GPGD - ldx.d t0, t1, t0 + /* Load PGD for KVM hypervisor */ + ld.d t0, a2, KVM_ARCH_KVMPGD csrwr t0, LOONGARCH_CSR_PGDL /* Mix GID and RID */ @@ -85,7 +77,7 @@ * Guest CRMD comes from separate GCSR_CRMD register */ ori t0, zero, CSR_PRMD_PIE - csrxchg t0, t0, LOONGARCH_CSR_PRMD + csrwr t0, LOONGARCH_CSR_PRMD /* Set PVM bit to setup ertn to guest context */ ori t0, zero, CSR_GSTAT_PVM diff --git a/arch/loongarch/kvm/trace.h b/arch/loongarch/kvm/trace.h index 1783397b1bc8..145514dab6d5 100644 --- a/arch/loongarch/kvm/trace.h +++ b/arch/loongarch/kvm/trace.h @@ -46,11 +46,15 @@ DEFINE_EVENT(kvm_transition, kvm_out, /* Further exit reasons */ #define KVM_TRACE_EXIT_IDLE 64 #define KVM_TRACE_EXIT_CACHE 65 +#define KVM_TRACE_EXIT_CPUCFG 66 +#define KVM_TRACE_EXIT_CSR 67 /* Tracepoints for VM exits */ #define kvm_trace_symbol_exit_types \ { KVM_TRACE_EXIT_IDLE, "IDLE" }, \ - { KVM_TRACE_EXIT_CACHE, "CACHE" } + { KVM_TRACE_EXIT_CACHE, "CACHE" }, \ + { KVM_TRACE_EXIT_CPUCFG, "CPUCFG" }, \ + { KVM_TRACE_EXIT_CSR, "CSR" } DECLARE_EVENT_CLASS(kvm_exit, TP_PROTO(struct kvm_vcpu *vcpu, unsigned int reason), @@ -82,6 +86,14 @@ DEFINE_EVENT(kvm_exit, kvm_exit_cache, TP_PROTO(struct kvm_vcpu *vcpu, unsigned int reason), TP_ARGS(vcpu, reason)); +DEFINE_EVENT(kvm_exit, kvm_exit_cpucfg, + TP_PROTO(struct kvm_vcpu *vcpu, unsigned int reason), + TP_ARGS(vcpu, reason)); + +DEFINE_EVENT(kvm_exit, kvm_exit_csr, + TP_PROTO(struct kvm_vcpu *vcpu, unsigned int reason), + TP_ARGS(vcpu, reason)); + DEFINE_EVENT(kvm_exit, kvm_exit, TP_PROTO(struct kvm_vcpu *vcpu, unsigned int reason), TP_ARGS(vcpu, reason)); diff --git a/arch/loongarch/kvm/vcpu.c b/arch/loongarch/kvm/vcpu.c index d18a4a270415..d1b8c50941ca 100644 --- a/arch/loongarch/kvm/vcpu.c +++ b/arch/loongarch/kvm/vcpu.c @@ -20,7 +20,13 @@ const struct _kvm_stats_desc kvm_vcpu_stats_desc[] = { STATS_DESC_COUNTER(VCPU, idle_exits), STATS_DESC_COUNTER(VCPU, cpucfg_exits), STATS_DESC_COUNTER(VCPU, signal_exits), - STATS_DESC_COUNTER(VCPU, hypercall_exits) + STATS_DESC_COUNTER(VCPU, hypercall_exits), + STATS_DESC_COUNTER(VCPU, ipi_read_exits), + STATS_DESC_COUNTER(VCPU, ipi_write_exits), + STATS_DESC_COUNTER(VCPU, eiointc_read_exits), + STATS_DESC_COUNTER(VCPU, eiointc_write_exits), + STATS_DESC_COUNTER(VCPU, pch_pic_read_exits), + STATS_DESC_COUNTER(VCPU, pch_pic_write_exits) }; const struct kvm_stats_header kvm_vcpu_stats_header = { @@ -294,6 +300,7 @@ static int kvm_pre_enter_guest(struct kvm_vcpu *vcpu) vcpu->arch.aux_inuse &= ~KVM_LARCH_SWCSR_LATEST; if (kvm_request_pending(vcpu) || xfer_to_guest_mode_work_pending()) { + kvm_lose_pmu(vcpu); /* make sure the vcpu mode has been written */ smp_store_mb(vcpu->mode, OUTSIDE_GUEST_MODE); local_irq_enable(); @@ -311,7 +318,7 @@ static int kvm_handle_exit(struct kvm_run *run, struct kvm_vcpu *vcpu) { int ret = RESUME_GUEST; unsigned long estat = vcpu->arch.host_estat; - u32 intr = estat & 0x1fff; /* Ignore NMI */ + u32 intr = estat & CSR_ESTAT_IS; u32 ecode = (estat & CSR_ESTAT_EXC) >> CSR_ESTAT_EXC_SHIFT; vcpu->mode = OUTSIDE_GUEST_MODE; @@ -361,6 +368,34 @@ int kvm_arch_vcpu_should_kick(struct kvm_vcpu *vcpu) bool kvm_arch_vcpu_in_kernel(struct kvm_vcpu *vcpu) { + unsigned long val; + + preempt_disable(); + val = gcsr_read(LOONGARCH_CSR_CRMD); + preempt_enable(); + + return (val & CSR_PRMD_PPLV) == PLV_KERN; +} + +#ifdef CONFIG_GUEST_PERF_EVENTS +unsigned long kvm_arch_vcpu_get_ip(struct kvm_vcpu *vcpu) +{ + return vcpu->arch.pc; +} + +/* + * Returns true if a Performance Monitoring Interrupt (PMI), a.k.a. perf event, + * arrived in guest context. For LoongArch64, if PMU is not passthrough to VM, + * any event that arrives while a vCPU is loaded is considered to be "in guest". + */ +bool kvm_arch_pmi_in_guest(struct kvm_vcpu *vcpu) +{ + return (vcpu && !(vcpu->arch.aux_inuse & KVM_LARCH_PMU)); +} +#endif + +bool kvm_arch_vcpu_preempted_in_kernel(struct kvm_vcpu *vcpu) +{ return false; } @@ -874,6 +909,13 @@ static int kvm_set_one_reg(struct kvm_vcpu *vcpu, vcpu->arch.st.guest_addr = 0; memset(&vcpu->arch.irq_pending, 0, sizeof(vcpu->arch.irq_pending)); memset(&vcpu->arch.irq_clear, 0, sizeof(vcpu->arch.irq_clear)); + + /* + * When vCPU reset, clear the ESTAT and GINTC registers + * Other CSR registers are cleared with function _kvm_setcsr(). + */ + kvm_write_sw_gcsr(vcpu->arch.csr, LOONGARCH_CSR_GINTC, 0); + kvm_write_sw_gcsr(vcpu->arch.csr, LOONGARCH_CSR_ESTAT, 0); break; default: ret = -EINVAL; @@ -1459,8 +1501,17 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu) vcpu->arch.vpid = 0; vcpu->arch.flush_gpa = INVALID_GPA; - hrtimer_init(&vcpu->arch.swtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED_HARD); - vcpu->arch.swtimer.function = kvm_swtimer_wakeup; + hrtimer_setup(&vcpu->arch.swtimer, kvm_swtimer_wakeup, CLOCK_MONOTONIC, + HRTIMER_MODE_ABS_PINNED_HARD); + + /* Get GPA (=HVA) of PGD for kvm hypervisor */ + vcpu->arch.kvm_pgd = __pa(vcpu->kvm->arch.pgd); + + /* + * Get PGD for primary mmu, virtual address is used since there is + * memory access after loading from CSR_PGD in tlb exception fast path. + */ + vcpu->arch.host_pgd = (unsigned long)vcpu->kvm->mm->pgd; vcpu->arch.handle_exit = kvm_handle_exit; vcpu->arch.guest_eentry = (unsigned long)kvm_loongarch_ops->exc_entry; @@ -1548,9 +1599,6 @@ static int _kvm_vcpu_load(struct kvm_vcpu *vcpu, int cpu) /* Restore timer state regardless */ kvm_restore_timer(vcpu); - - /* Control guest page CCA attribute */ - change_csr_gcfg(CSR_GCFG_MATC_MASK, CSR_GCFG_MATC_ROOT); kvm_make_request(KVM_REQ_STEAL_UPDATE, vcpu); /* Restore hardware PMU CSRs */ @@ -1732,9 +1780,14 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu) vcpu->mmio_needed = 0; } - if (run->exit_reason == KVM_EXIT_LOONGARCH_IOCSR) { + switch (run->exit_reason) { + case KVM_EXIT_HYPERCALL: + kvm_complete_user_service(vcpu, run); + break; + case KVM_EXIT_LOONGARCH_IOCSR: if (!run->iocsr_io.is_write) kvm_complete_iocsr_read(vcpu, run); + break; } if (!vcpu->wants_to_run) diff --git a/arch/loongarch/kvm/vm.c b/arch/loongarch/kvm/vm.c index b8b3e1972d6e..edccfc8c9cd8 100644 --- a/arch/loongarch/kvm/vm.c +++ b/arch/loongarch/kvm/vm.c @@ -48,7 +48,11 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) if (kvm_pvtime_supported()) kvm->arch.pv_features |= BIT(KVM_FEATURE_STEAL_TIME); - kvm->arch.gpa_size = BIT(cpu_vabits - 1); + /* + * cpu_vabits means user address space only (a half of total). + * GPA size of VM is the same with the size of user address space. + */ + kvm->arch.gpa_size = BIT(cpu_vabits); kvm->arch.root_level = CONFIG_PGTABLE_LEVELS - 1; kvm->arch.invalid_ptes[0] = 0; kvm->arch.invalid_ptes[1] = (unsigned long)invalid_pte_table; |