// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2020-2023 Loongson Technology Corporation Limited */ #include #include #include #include #include #include #include "trace.h" unsigned long vpid_mask; struct kvm_world_switch *kvm_loongarch_ops; static int gcsr_flag[CSR_MAX_NUMS]; static struct kvm_context __percpu *vmcs; int get_gcsr_flag(int csr) { if (csr < CSR_MAX_NUMS) return gcsr_flag[csr]; return INVALID_GCSR; } static inline void set_gcsr_sw_flag(int csr) { if (csr < CSR_MAX_NUMS) gcsr_flag[csr] |= SW_GCSR; } static inline void set_gcsr_hw_flag(int csr) { if (csr < CSR_MAX_NUMS) gcsr_flag[csr] |= HW_GCSR; } /* * The default value of gcsr_flag[CSR] is 0, and we use this * function to set the flag to 1 (SW_GCSR) or 2 (HW_GCSR) if the * gcsr is software or hardware. It will be used by get/set_gcsr, * if gcsr_flag is HW we should use gcsrrd/gcsrwr to access it, * else use software csr to emulate it. */ static void kvm_init_gcsr_flag(void) { set_gcsr_hw_flag(LOONGARCH_CSR_CRMD); set_gcsr_hw_flag(LOONGARCH_CSR_PRMD); set_gcsr_hw_flag(LOONGARCH_CSR_EUEN); set_gcsr_hw_flag(LOONGARCH_CSR_MISC); set_gcsr_hw_flag(LOONGARCH_CSR_ECFG); set_gcsr_hw_flag(LOONGARCH_CSR_ESTAT); set_gcsr_hw_flag(LOONGARCH_CSR_ERA); set_gcsr_hw_flag(LOONGARCH_CSR_BADV); set_gcsr_hw_flag(LOONGARCH_CSR_BADI); set_gcsr_hw_flag(LOONGARCH_CSR_EENTRY); set_gcsr_hw_flag(LOONGARCH_CSR_TLBIDX); set_gcsr_hw_flag(LOONGARCH_CSR_TLBEHI); set_gcsr_hw_flag(LOONGARCH_CSR_TLBELO0); set_gcsr_hw_flag(LOONGARCH_CSR_TLBELO1); set_gcsr_hw_flag(LOONGARCH_CSR_ASID); set_gcsr_hw_flag(LOONGARCH_CSR_PGDL); set_gcsr_hw_flag(LOONGARCH_CSR_PGDH); set_gcsr_hw_flag(LOONGARCH_CSR_PGD); set_gcsr_hw_flag(LOONGARCH_CSR_PWCTL0); set_gcsr_hw_flag(LOONGARCH_CSR_PWCTL1); set_gcsr_hw_flag(LOONGARCH_CSR_STLBPGSIZE); set_gcsr_hw_flag(LOONGARCH_CSR_RVACFG); set_gcsr_hw_flag(LOONGARCH_CSR_CPUID); set_gcsr_hw_flag(LOONGARCH_CSR_PRCFG1); set_gcsr_hw_flag(LOONGARCH_CSR_PRCFG2); set_gcsr_hw_flag(LOONGARCH_CSR_PRCFG3); set_gcsr_hw_flag(LOONGARCH_CSR_KS0); set_gcsr_hw_flag(LOONGARCH_CSR_KS1); set_gcsr_hw_flag(LOONGARCH_CSR_KS2); set_gcsr_hw_flag(LOONGARCH_CSR_KS3); set_gcsr_hw_flag(LOONGARCH_CSR_KS4); set_gcsr_hw_flag(LOONGARCH_CSR_KS5); set_gcsr_hw_flag(LOONGARCH_CSR_KS6); set_gcsr_hw_flag(LOONGARCH_CSR_KS7); set_gcsr_hw_flag(LOONGARCH_CSR_TMID); set_gcsr_hw_flag(LOONGARCH_CSR_TCFG); set_gcsr_hw_flag(LOONGARCH_CSR_TVAL); set_gcsr_hw_flag(LOONGARCH_CSR_TINTCLR); set_gcsr_hw_flag(LOONGARCH_CSR_CNTC); set_gcsr_hw_flag(LOONGARCH_CSR_LLBCTL); set_gcsr_hw_flag(LOONGARCH_CSR_TLBRENTRY); set_gcsr_hw_flag(LOONGARCH_CSR_TLBRBADV); set_gcsr_hw_flag(LOONGARCH_CSR_TLBRERA); set_gcsr_hw_flag(LOONGARCH_CSR_TLBRSAVE); set_gcsr_hw_flag(LOONGARCH_CSR_TLBRELO0); set_gcsr_hw_flag(LOONGARCH_CSR_TLBRELO1); set_gcsr_hw_flag(LOONGARCH_CSR_TLBREHI); set_gcsr_hw_flag(LOONGARCH_CSR_TLBRPRMD); set_gcsr_hw_flag(LOONGARCH_CSR_DMWIN0); set_gcsr_hw_flag(LOONGARCH_CSR_DMWIN1); set_gcsr_hw_flag(LOONGARCH_CSR_DMWIN2); set_gcsr_hw_flag(LOONGARCH_CSR_DMWIN3); set_gcsr_sw_flag(LOONGARCH_CSR_IMPCTL1); set_gcsr_sw_flag(LOONGARCH_CSR_IMPCTL2); set_gcsr_sw_flag(LOONGARCH_CSR_MERRCTL); set_gcsr_sw_flag(LOONGARCH_CSR_MERRINFO1); set_gcsr_sw_flag(LOONGARCH_CSR_MERRINFO2); set_gcsr_sw_flag(LOONGARCH_CSR_MERRENTRY); set_gcsr_sw_flag(LOONGARCH_CSR_MERRERA); set_gcsr_sw_flag(LOONGARCH_CSR_MERRSAVE); set_gcsr_sw_flag(LOONGARCH_CSR_CTAG); set_gcsr_sw_flag(LOONGARCH_CSR_DEBUG); set_gcsr_sw_flag(LOONGARCH_CSR_DERA); set_gcsr_sw_flag(LOONGARCH_CSR_DESAVE); set_gcsr_sw_flag(LOONGARCH_CSR_FWPC); set_gcsr_sw_flag(LOONGARCH_CSR_FWPS); set_gcsr_sw_flag(LOONGARCH_CSR_MWPC); set_gcsr_sw_flag(LOONGARCH_CSR_MWPS); set_gcsr_sw_flag(LOONGARCH_CSR_DB0ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_DB0MASK); set_gcsr_sw_flag(LOONGARCH_CSR_DB0CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_DB0ASID); set_gcsr_sw_flag(LOONGARCH_CSR_DB1ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_DB1MASK); set_gcsr_sw_flag(LOONGARCH_CSR_DB1CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_DB1ASID); set_gcsr_sw_flag(LOONGARCH_CSR_DB2ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_DB2MASK); set_gcsr_sw_flag(LOONGARCH_CSR_DB2CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_DB2ASID); set_gcsr_sw_flag(LOONGARCH_CSR_DB3ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_DB3MASK); set_gcsr_sw_flag(LOONGARCH_CSR_DB3CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_DB3ASID); set_gcsr_sw_flag(LOONGARCH_CSR_DB4ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_DB4MASK); set_gcsr_sw_flag(LOONGARCH_CSR_DB4CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_DB4ASID); set_gcsr_sw_flag(LOONGARCH_CSR_DB5ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_DB5MASK); set_gcsr_sw_flag(LOONGARCH_CSR_DB5CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_DB5ASID); set_gcsr_sw_flag(LOONGARCH_CSR_DB6ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_DB6MASK); set_gcsr_sw_flag(LOONGARCH_CSR_DB6CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_DB6ASID); set_gcsr_sw_flag(LOONGARCH_CSR_DB7ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_DB7MASK); set_gcsr_sw_flag(LOONGARCH_CSR_DB7CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_DB7ASID); set_gcsr_sw_flag(LOONGARCH_CSR_IB0ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_IB0MASK); set_gcsr_sw_flag(LOONGARCH_CSR_IB0CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_IB0ASID); set_gcsr_sw_flag(LOONGARCH_CSR_IB1ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_IB1MASK); set_gcsr_sw_flag(LOONGARCH_CSR_IB1CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_IB1ASID); set_gcsr_sw_flag(LOONGARCH_CSR_IB2ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_IB2MASK); set_gcsr_sw_flag(LOONGARCH_CSR_IB2CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_IB2ASID); set_gcsr_sw_flag(LOONGARCH_CSR_IB3ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_IB3MASK); set_gcsr_sw_flag(LOONGARCH_CSR_IB3CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_IB3ASID); set_gcsr_sw_flag(LOONGARCH_CSR_IB4ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_IB4MASK); set_gcsr_sw_flag(LOONGARCH_CSR_IB4CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_IB4ASID); set_gcsr_sw_flag(LOONGARCH_CSR_IB5ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_IB5MASK); set_gcsr_sw_flag(LOONGARCH_CSR_IB5CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_IB5ASID); set_gcsr_sw_flag(LOONGARCH_CSR_IB6ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_IB6MASK); set_gcsr_sw_flag(LOONGARCH_CSR_IB6CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_IB6ASID); set_gcsr_sw_flag(LOONGARCH_CSR_IB7ADDR); set_gcsr_sw_flag(LOONGARCH_CSR_IB7MASK); set_gcsr_sw_flag(LOONGARCH_CSR_IB7CTRL); set_gcsr_sw_flag(LOONGARCH_CSR_IB7ASID); set_gcsr_sw_flag(LOONGARCH_CSR_PERFCTRL0); set_gcsr_sw_flag(LOONGARCH_CSR_PERFCNTR0); set_gcsr_sw_flag(LOONGARCH_CSR_PERFCTRL1); set_gcsr_sw_flag(LOONGARCH_CSR_PERFCNTR1); set_gcsr_sw_flag(LOONGARCH_CSR_PERFCTRL2); set_gcsr_sw_flag(LOONGARCH_CSR_PERFCNTR2); set_gcsr_sw_flag(LOONGARCH_CSR_PERFCTRL3); set_gcsr_sw_flag(LOONGARCH_CSR_PERFCNTR3); } static void kvm_update_vpid(struct kvm_vcpu *vcpu, int cpu) { unsigned long vpid; struct kvm_context *context; context = per_cpu_ptr(vcpu->kvm->arch.vmcs, cpu); vpid = context->vpid_cache + 1; if (!(vpid & vpid_mask)) { /* finish round of vpid loop */ if (unlikely(!vpid)) vpid = vpid_mask + 1; ++vpid; /* vpid 0 reserved for root */ /* start new vpid cycle */ kvm_flush_tlb_all(); } context->vpid_cache = vpid; vcpu->arch.vpid = vpid; } void kvm_check_vpid(struct kvm_vcpu *vcpu) { int cpu; bool migrated; unsigned long ver, old, vpid; struct kvm_context *context; cpu = smp_processor_id(); /* * Are we entering guest context on a different CPU to last time? * If so, the vCPU's guest TLB state on this CPU may be stale. */ context = per_cpu_ptr(vcpu->kvm->arch.vmcs, cpu); migrated = (vcpu->cpu != cpu); /* * Check if our vpid is of an older version * * We also discard the stored vpid if we've executed on * another CPU, as the guest mappings may have changed without * hypervisor knowledge. */ ver = vcpu->arch.vpid & ~vpid_mask; old = context->vpid_cache & ~vpid_mask; if (migrated || (ver != old)) { kvm_update_vpid(vcpu, cpu); trace_kvm_vpid_change(vcpu, vcpu->arch.vpid); vcpu->cpu = cpu; } /* Restore GSTAT(0x50).vpid */ vpid = (vcpu->arch.vpid & vpid_mask) << CSR_GSTAT_GID_SHIFT; change_csr_gstat(vpid_mask << CSR_GSTAT_GID_SHIFT, vpid); } void kvm_init_vmcs(struct kvm *kvm) { kvm->arch.vmcs = vmcs; } long kvm_arch_dev_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) { return -ENOIOCTLCMD; } int kvm_arch_hardware_enable(void) { unsigned long env, gcfg = 0; env = read_csr_gcfg(); /* First init gcfg, gstat, gintc, gtlbc. All guest use the same config */ write_csr_gcfg(0); write_csr_gstat(0); write_csr_gintc(0); clear_csr_gtlbc(CSR_GTLBC_USETGID | CSR_GTLBC_TOTI); /* * Enable virtualization features granting guest direct control of * certain features: * GCI=2: Trap on init or unimplement cache instruction. * TORU=0: Trap on Root Unimplement. * CACTRL=1: Root control cache. * TOP=0: Trap on Previlege. * TOE=0: Trap on Exception. * TIT=0: Trap on Timer. */ if (env & CSR_GCFG_GCIP_ALL) gcfg |= CSR_GCFG_GCI_SECURE; if (env & CSR_GCFG_MATC_ROOT) gcfg |= CSR_GCFG_MATC_ROOT; gcfg |= CSR_GCFG_TIT; write_csr_gcfg(gcfg); kvm_flush_tlb_all(); /* Enable using TGID */ set_csr_gtlbc(CSR_GTLBC_USETGID); kvm_debug("GCFG:%lx GSTAT:%lx GINTC:%lx GTLBC:%lx", read_csr_gcfg(), read_csr_gstat(), read_csr_gintc(), read_csr_gtlbc()); return 0; } void kvm_arch_hardware_disable(void) { write_csr_gcfg(0); write_csr_gstat(0); write_csr_gintc(0); clear_csr_gtlbc(CSR_GTLBC_USETGID | CSR_GTLBC_TOTI); /* Flush any remaining guest TLB entries */ kvm_flush_tlb_all(); } static int kvm_loongarch_env_init(void) { int cpu, order; void *addr; struct kvm_context *context; vmcs = alloc_percpu(struct kvm_context); if (!vmcs) { pr_err("kvm: failed to allocate percpu kvm_context\n"); return -ENOMEM; } kvm_loongarch_ops = kzalloc(sizeof(*kvm_loongarch_ops), GFP_KERNEL); if (!kvm_loongarch_ops) { free_percpu(vmcs); vmcs = NULL; return -ENOMEM; } /* * PGD register is shared between root kernel and kvm hypervisor. * So world switch entry should be in DMW area rather than TLB area * to avoid page fault reenter. * * In future if hardware pagetable walking is supported, we won't * need to copy world switch code to DMW area. */ order = get_order(kvm_exception_size + kvm_enter_guest_size); addr = (void *)__get_free_pages(GFP_KERNEL, order); if (!addr) { free_percpu(vmcs); vmcs = NULL; kfree(kvm_loongarch_ops); kvm_loongarch_ops = NULL; return -ENOMEM; } memcpy(addr, kvm_exc_entry, kvm_exception_size); memcpy(addr + kvm_exception_size, kvm_enter_guest, kvm_enter_guest_size); flush_icache_range((unsigned long)addr, (unsigned long)addr + kvm_exception_size + kvm_enter_guest_size); kvm_loongarch_ops->exc_entry = addr; kvm_loongarch_ops->enter_guest = addr + kvm_exception_size; kvm_loongarch_ops->page_order = order; vpid_mask = read_csr_gstat(); vpid_mask = (vpid_mask & CSR_GSTAT_GIDBIT) >> CSR_GSTAT_GIDBIT_SHIFT; if (vpid_mask) vpid_mask = GENMASK(vpid_mask - 1, 0); for_each_possible_cpu(cpu) { context = per_cpu_ptr(vmcs, cpu); context->vpid_cache = vpid_mask + 1; context->last_vcpu = NULL; } kvm_init_gcsr_flag(); return 0; } static void kvm_loongarch_env_exit(void) { unsigned long addr; if (vmcs) free_percpu(vmcs); if (kvm_loongarch_ops) { if (kvm_loongarch_ops->exc_entry) { addr = (unsigned long)kvm_loongarch_ops->exc_entry; free_pages(addr, kvm_loongarch_ops->page_order); } kfree(kvm_loongarch_ops); } } static int kvm_loongarch_init(void) { int r; if (!cpu_has_lvz) { kvm_info("Hardware virtualization not available\n"); return -ENODEV; } r = kvm_loongarch_env_init(); if (r) return r; return kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE); } static void kvm_loongarch_exit(void) { kvm_exit(); kvm_loongarch_env_exit(); } module_init(kvm_loongarch_init); module_exit(kvm_loongarch_exit); #ifdef MODULE static const struct cpu_feature kvm_feature[] = { { .feature = cpu_feature(LOONGARCH_LVZ) }, {}, }; MODULE_DEVICE_TABLE(cpu, kvm_feature); #endif