// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2022 Ventana Micro Systems Inc. */ #include <linux/bitmap.h> #include <linux/cpumask.h> #include <linux/errno.h> #include <linux/err.h> #include <linux/module.h> #include <linux/smp.h> #include <linux/kvm_host.h> #include <asm/cacheflush.h> #include <asm/csr.h> #include <asm/hwcap.h> #include <asm/insn-def.h> #define has_svinval() riscv_has_extension_unlikely(RISCV_ISA_EXT_SVINVAL) void kvm_riscv_local_hfence_gvma_vmid_gpa(unsigned long vmid, gpa_t gpa, gpa_t gpsz, unsigned long order) { gpa_t pos; if (PTRS_PER_PTE < (gpsz >> order)) { kvm_riscv_local_hfence_gvma_vmid_all(vmid); return; } if (has_svinval()) { asm volatile (SFENCE_W_INVAL() ::: "memory"); for (pos = gpa; pos < (gpa + gpsz); pos += BIT(order)) asm volatile (HINVAL_GVMA(%0, %1) : : "r" (pos >> 2), "r" (vmid) : "memory"); asm volatile (SFENCE_INVAL_IR() ::: "memory"); } else { for (pos = gpa; pos < (gpa + gpsz); pos += BIT(order)) asm volatile (HFENCE_GVMA(%0, %1) : : "r" (pos >> 2), "r" (vmid) : "memory"); } } void kvm_riscv_local_hfence_gvma_vmid_all(unsigned long vmid) { asm volatile(HFENCE_GVMA(zero, %0) : : "r" (vmid) : "memory"); } void kvm_riscv_local_hfence_gvma_gpa(gpa_t gpa, gpa_t gpsz, unsigned long order) { gpa_t pos; if (PTRS_PER_PTE < (gpsz >> order)) { kvm_riscv_local_hfence_gvma_all(); return; } if (has_svinval()) { asm volatile (SFENCE_W_INVAL() ::: "memory"); for (pos = gpa; pos < (gpa + gpsz); pos += BIT(order)) asm volatile(HINVAL_GVMA(%0, zero) : : "r" (pos >> 2) : "memory"); asm volatile (SFENCE_INVAL_IR() ::: "memory"); } else { for (pos = gpa; pos < (gpa + gpsz); pos += BIT(order)) asm volatile(HFENCE_GVMA(%0, zero) : : "r" (pos >> 2) : "memory"); } } void kvm_riscv_local_hfence_gvma_all(void) { asm volatile(HFENCE_GVMA(zero, zero) : : : "memory"); } void kvm_riscv_local_hfence_vvma_asid_gva(unsigned long vmid, unsigned long asid, unsigned long gva, unsigned long gvsz, unsigned long order) { unsigned long pos, hgatp; if (PTRS_PER_PTE < (gvsz >> order)) { kvm_riscv_local_hfence_vvma_asid_all(vmid, asid); return; } hgatp = csr_swap(CSR_HGATP, vmid << HGATP_VMID_SHIFT); if (has_svinval()) { asm volatile (SFENCE_W_INVAL() ::: "memory"); for (pos = gva; pos < (gva + gvsz); pos += BIT(order)) asm volatile(HINVAL_VVMA(%0, %1) : : "r" (pos), "r" (asid) : "memory"); asm volatile (SFENCE_INVAL_IR() ::: "memory"); } else { for (pos = gva; pos < (gva + gvsz); pos += BIT(order)) asm volatile(HFENCE_VVMA(%0, %1) : : "r" (pos), "r" (asid) : "memory"); } csr_write(CSR_HGATP, hgatp); } void kvm_riscv_local_hfence_vvma_asid_all(unsigned long vmid, unsigned long asid) { unsigned long hgatp; hgatp = csr_swap(CSR_HGATP, vmid << HGATP_VMID_SHIFT); asm volatile(HFENCE_VVMA(zero, %0) : : "r" (asid) : "memory"); csr_write(CSR_HGATP, hgatp); } void kvm_riscv_local_hfence_vvma_gva(unsigned long vmid, unsigned long gva, unsigned long gvsz, unsigned long order) { unsigned long pos, hgatp; if (PTRS_PER_PTE < (gvsz >> order)) { kvm_riscv_local_hfence_vvma_all(vmid); return; } hgatp = csr_swap(CSR_HGATP, vmid << HGATP_VMID_SHIFT); if (has_svinval()) { asm volatile (SFENCE_W_INVAL() ::: "memory"); for (pos = gva; pos < (gva + gvsz); pos += BIT(order)) asm volatile(HINVAL_VVMA(%0, zero) : : "r" (pos) : "memory"); asm volatile (SFENCE_INVAL_IR() ::: "memory"); } else { for (pos = gva; pos < (gva + gvsz); pos += BIT(order)) asm volatile(HFENCE_VVMA(%0, zero) : : "r" (pos) : "memory"); } csr_write(CSR_HGATP, hgatp); } void kvm_riscv_local_hfence_vvma_all(unsigned long vmid) { unsigned long hgatp; hgatp = csr_swap(CSR_HGATP, vmid << HGATP_VMID_SHIFT); asm volatile(HFENCE_VVMA(zero, zero) : : : "memory"); csr_write(CSR_HGATP, hgatp); } void kvm_riscv_local_tlb_sanitize(struct kvm_vcpu *vcpu) { unsigned long vmid; if (!kvm_riscv_gstage_vmid_bits() || vcpu->arch.last_exit_cpu == vcpu->cpu) return; /* * On RISC-V platforms with hardware VMID support, we share same * VMID for all VCPUs of a particular Guest/VM. This means we might * have stale G-stage TLB entries on the current Host CPU due to * some other VCPU of the same Guest which ran previously on the * current Host CPU. * * To cleanup stale TLB entries, we simply flush all G-stage TLB * entries by VMID whenever underlying Host CPU changes for a VCPU. */ vmid = READ_ONCE(vcpu->kvm->arch.vmid.vmid); kvm_riscv_local_hfence_gvma_vmid_all(vmid); } void kvm_riscv_fence_i_process(struct kvm_vcpu *vcpu) { kvm_riscv_vcpu_pmu_incr_fw(vcpu, SBI_PMU_FW_FENCE_I_RCVD); local_flush_icache_all(); } void kvm_riscv_hfence_gvma_vmid_all_process(struct kvm_vcpu *vcpu) { struct kvm_vmid *vmid; vmid = &vcpu->kvm->arch.vmid; kvm_riscv_local_hfence_gvma_vmid_all(READ_ONCE(vmid->vmid)); } void kvm_riscv_hfence_vvma_all_process(struct kvm_vcpu *vcpu) { struct kvm_vmid *vmid; vmid = &vcpu->kvm->arch.vmid; kvm_riscv_local_hfence_vvma_all(READ_ONCE(vmid->vmid)); } static bool vcpu_hfence_dequeue(struct kvm_vcpu *vcpu, struct kvm_riscv_hfence *out_data) { bool ret = false; struct kvm_vcpu_arch *varch = &vcpu->arch; spin_lock(&varch->hfence_lock); if (varch->hfence_queue[varch->hfence_head].type) { memcpy(out_data, &varch->hfence_queue[varch->hfence_head], sizeof(*out_data)); varch->hfence_queue[varch->hfence_head].type = 0; varch->hfence_head++; if (varch->hfence_head == KVM_RISCV_VCPU_MAX_HFENCE) varch->hfence_head = 0; ret = true; } spin_unlock(&varch->hfence_lock); return ret; } static bool vcpu_hfence_enqueue(struct kvm_vcpu *vcpu, const struct kvm_riscv_hfence *data) { bool ret = false; struct kvm_vcpu_arch *varch = &vcpu->arch; spin_lock(&varch->hfence_lock); if (!varch->hfence_queue[varch->hfence_tail].type) { memcpy(&varch->hfence_queue[varch->hfence_tail], data, sizeof(*data)); varch->hfence_tail++; if (varch->hfence_tail == KVM_RISCV_VCPU_MAX_HFENCE) varch->hfence_tail = 0; ret = true; } spin_unlock(&varch->hfence_lock); return ret; } void kvm_riscv_hfence_process(struct kvm_vcpu *vcpu) { struct kvm_riscv_hfence d = { 0 }; struct kvm_vmid *v = &vcpu->kvm->arch.vmid; while (vcpu_hfence_dequeue(vcpu, &d)) { switch (d.type) { case KVM_RISCV_HFENCE_UNKNOWN: break; case KVM_RISCV_HFENCE_GVMA_VMID_GPA: kvm_riscv_local_hfence_gvma_vmid_gpa( READ_ONCE(v->vmid), d.addr, d.size, d.order); break; case KVM_RISCV_HFENCE_VVMA_ASID_GVA: kvm_riscv_vcpu_pmu_incr_fw(vcpu, SBI_PMU_FW_HFENCE_VVMA_ASID_RCVD); kvm_riscv_local_hfence_vvma_asid_gva( READ_ONCE(v->vmid), d.asid, d.addr, d.size, d.order); break; case KVM_RISCV_HFENCE_VVMA_ASID_ALL: kvm_riscv_vcpu_pmu_incr_fw(vcpu, SBI_PMU_FW_HFENCE_VVMA_ASID_RCVD); kvm_riscv_local_hfence_vvma_asid_all( READ_ONCE(v->vmid), d.asid); break; case KVM_RISCV_HFENCE_VVMA_GVA: kvm_riscv_vcpu_pmu_incr_fw(vcpu, SBI_PMU_FW_HFENCE_VVMA_RCVD); kvm_riscv_local_hfence_vvma_gva( READ_ONCE(v->vmid), d.addr, d.size, d.order); break; default: break; } } } static void make_xfence_request(struct kvm *kvm, unsigned long hbase, unsigned long hmask, unsigned int req, unsigned int fallback_req, const struct kvm_riscv_hfence *data) { unsigned long i; struct kvm_vcpu *vcpu; unsigned int actual_req = req; DECLARE_BITMAP(vcpu_mask, KVM_MAX_VCPUS); bitmap_zero(vcpu_mask, KVM_MAX_VCPUS); kvm_for_each_vcpu(i, vcpu, kvm) { if (hbase != -1UL) { if (vcpu->vcpu_id < hbase) continue; if (!(hmask & (1UL << (vcpu->vcpu_id - hbase)))) continue; } bitmap_set(vcpu_mask, i, 1); if (!data || !data->type) continue; /* * Enqueue hfence data to VCPU hfence queue. If we don't * have space in the VCPU hfence queue then fallback to * a more conservative hfence request. */ if (!vcpu_hfence_enqueue(vcpu, data)) actual_req = fallback_req; } kvm_make_vcpus_request_mask(kvm, actual_req, vcpu_mask); } void kvm_riscv_fence_i(struct kvm *kvm, unsigned long hbase, unsigned long hmask) { make_xfence_request(kvm, hbase, hmask, KVM_REQ_FENCE_I, KVM_REQ_FENCE_I, NULL); } void kvm_riscv_hfence_gvma_vmid_gpa(struct kvm *kvm, unsigned long hbase, unsigned long hmask, gpa_t gpa, gpa_t gpsz, unsigned long order) { struct kvm_riscv_hfence data; data.type = KVM_RISCV_HFENCE_GVMA_VMID_GPA; data.asid = 0; data.addr = gpa; data.size = gpsz; data.order = order; make_xfence_request(kvm, hbase, hmask, KVM_REQ_HFENCE, KVM_REQ_HFENCE_GVMA_VMID_ALL, &data); } void kvm_riscv_hfence_gvma_vmid_all(struct kvm *kvm, unsigned long hbase, unsigned long hmask) { make_xfence_request(kvm, hbase, hmask, KVM_REQ_HFENCE_GVMA_VMID_ALL, KVM_REQ_HFENCE_GVMA_VMID_ALL, NULL); } void kvm_riscv_hfence_vvma_asid_gva(struct kvm *kvm, unsigned long hbase, unsigned long hmask, unsigned long gva, unsigned long gvsz, unsigned long order, unsigned long asid) { struct kvm_riscv_hfence data; data.type = KVM_RISCV_HFENCE_VVMA_ASID_GVA; data.asid = asid; data.addr = gva; data.size = gvsz; data.order = order; make_xfence_request(kvm, hbase, hmask, KVM_REQ_HFENCE, KVM_REQ_HFENCE_VVMA_ALL, &data); } void kvm_riscv_hfence_vvma_asid_all(struct kvm *kvm, unsigned long hbase, unsigned long hmask, unsigned long asid) { struct kvm_riscv_hfence data; data.type = KVM_RISCV_HFENCE_VVMA_ASID_ALL; data.asid = asid; data.addr = data.size = data.order = 0; make_xfence_request(kvm, hbase, hmask, KVM_REQ_HFENCE, KVM_REQ_HFENCE_VVMA_ALL, &data); } void kvm_riscv_hfence_vvma_gva(struct kvm *kvm, unsigned long hbase, unsigned long hmask, unsigned long gva, unsigned long gvsz, unsigned long order) { struct kvm_riscv_hfence data; data.type = KVM_RISCV_HFENCE_VVMA_GVA; data.asid = 0; data.addr = gva; data.size = gvsz; data.order = order; make_xfence_request(kvm, hbase, hmask, KVM_REQ_HFENCE, KVM_REQ_HFENCE_VVMA_ALL, &data); } void kvm_riscv_hfence_vvma_all(struct kvm *kvm, unsigned long hbase, unsigned long hmask) { make_xfence_request(kvm, hbase, hmask, KVM_REQ_HFENCE_VVMA_ALL, KVM_REQ_HFENCE_VVMA_ALL, NULL); }