This patch implements all required functions for programming
the stage2 page table for each Guest/VM.

At high-level, the flow of stage2 related functions is similar
from KVM ARM/ARM64 implementation but the stage2 page table
format is quite different for KVM RISC-V.

Signed-off-by: Anup Patel <anup.pa...@wdc.com>
Acked-by: Paolo Bonzini <pbonz...@redhat.com>
Reviewed-by: Paolo Bonzini <pbonz...@redhat.com>
---
 arch/riscv/include/asm/kvm_host.h     |  10 +
 arch/riscv/include/asm/pgtable-bits.h |   1 +
 arch/riscv/kvm/mmu.c                  | 643 +++++++++++++++++++++++++-
 3 files changed, 644 insertions(+), 10 deletions(-)

diff --git a/arch/riscv/include/asm/kvm_host.h 
b/arch/riscv/include/asm/kvm_host.h
index 8aaf22a900be..bc27f664b443 100644
--- a/arch/riscv/include/asm/kvm_host.h
+++ b/arch/riscv/include/asm/kvm_host.h
@@ -73,6 +73,13 @@ struct kvm_mmio_decode {
        int return_handled;
 };
 
+#define KVM_MMU_PAGE_CACHE_NR_OBJS     32
+
+struct kvm_mmu_page_cache {
+       int nobjs;
+       void *objects[KVM_MMU_PAGE_CACHE_NR_OBJS];
+};
+
 struct kvm_cpu_context {
        unsigned long zero;
        unsigned long ra;
@@ -164,6 +171,9 @@ struct kvm_vcpu_arch {
        /* MMIO instruction details */
        struct kvm_mmio_decode mmio_decode;
 
+       /* Cache pages needed to program page tables with spinlock held */
+       struct kvm_mmu_page_cache mmu_page_cache;
+
        /* VCPU power-off state */
        bool power_off;
 
diff --git a/arch/riscv/include/asm/pgtable-bits.h 
b/arch/riscv/include/asm/pgtable-bits.h
index bbaeb5d35842..be49d62fcc2b 100644
--- a/arch/riscv/include/asm/pgtable-bits.h
+++ b/arch/riscv/include/asm/pgtable-bits.h
@@ -26,6 +26,7 @@
 
 #define _PAGE_SPECIAL   _PAGE_SOFT
 #define _PAGE_TABLE     _PAGE_PRESENT
+#define _PAGE_LEAF      (_PAGE_READ | _PAGE_WRITE | _PAGE_EXEC)
 
 /*
  * _PAGE_PROT_NONE is set on not-present pages (and ignored by the hardware) to
diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
index 2b965f9aac07..590669290139 100644
--- a/arch/riscv/kvm/mmu.c
+++ b/arch/riscv/kvm/mmu.c
@@ -18,6 +18,438 @@
 #include <asm/page.h>
 #include <asm/pgtable.h>
 
+#ifdef CONFIG_64BIT
+#define stage2_have_pmd                true
+#define stage2_gpa_size                ((phys_addr_t)(1ULL << 39))
+#define stage2_cache_min_pages 2
+#else
+#define pmd_index(x)           0
+#define pfn_pmd(x, y)          ({ pmd_t __x = { 0 }; __x; })
+#define stage2_have_pmd                false
+#define stage2_gpa_size                ((phys_addr_t)(1ULL << 32))
+#define stage2_cache_min_pages 1
+#endif
+
+static int stage2_cache_topup(struct kvm_mmu_page_cache *pcache,
+                             int min, int max)
+{
+       void *page;
+
+       BUG_ON(max > KVM_MMU_PAGE_CACHE_NR_OBJS);
+       if (pcache->nobjs >= min)
+               return 0;
+       while (pcache->nobjs < max) {
+               page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
+               if (!page)
+                       return -ENOMEM;
+               pcache->objects[pcache->nobjs++] = page;
+       }
+
+       return 0;
+}
+
+static void stage2_cache_flush(struct kvm_mmu_page_cache *pcache)
+{
+       while (pcache && pcache->nobjs)
+               free_page((unsigned long)pcache->objects[--pcache->nobjs]);
+}
+
+static void *stage2_cache_alloc(struct kvm_mmu_page_cache *pcache)
+{
+       void *p;
+
+       if (!pcache)
+               return NULL;
+
+       BUG_ON(!pcache->nobjs);
+       p = pcache->objects[--pcache->nobjs];
+
+       return p;
+}
+
+struct local_guest_tlb_info {
+       struct kvm_vmid *vmid;
+       gpa_t addr;
+};
+
+static void local_guest_tlb_flush_vmid_gpa(void *info)
+{
+       struct local_guest_tlb_info *infop = info;
+
+       __kvm_riscv_hfence_gvma_vmid_gpa(READ_ONCE(infop->vmid->vmid_version),
+                                        infop->addr);
+}
+
+static void stage2_remote_tlb_flush(struct kvm *kvm, gpa_t addr)
+{
+       struct local_guest_tlb_info info;
+       struct kvm_vmid *vmid = &kvm->arch.vmid;
+
+       /*
+        * Ideally, we should have a SBI call OR some remote TLB instruction
+        * but we don't have it so we explicitly flush TLBs using IPIs.
+        *
+        * TODO: Instead of cpu_online_mask, we should only target CPUs
+        * where the Guest/VM is running.
+        */
+       info.vmid = vmid;
+       info.addr = addr;
+       preempt_disable();
+       smp_call_function_many(cpu_online_mask,
+                              local_guest_tlb_flush_vmid_gpa, &info, true);
+       preempt_enable();
+}
+
+static int stage2_set_pgd(struct kvm *kvm, gpa_t addr, const pgd_t *new_pgd)
+{
+       pgd_t *pgdp = &kvm->arch.pgd[pgd_index(addr)];
+
+       *pgdp = *new_pgd;
+       if (pgd_val(*pgdp) & _PAGE_LEAF)
+               stage2_remote_tlb_flush(kvm, addr);
+
+       return 0;
+}
+
+static int stage2_set_pmd(struct kvm *kvm, struct kvm_mmu_page_cache *pcache,
+                         gpa_t addr, const pmd_t *new_pmd)
+{
+       int rc;
+       pmd_t *pmdp;
+       pgd_t new_pgd;
+       pgd_t *pgdp = &kvm->arch.pgd[pgd_index(addr)];
+
+       if (!pgd_val(*pgdp)) {
+               pmdp = stage2_cache_alloc(pcache);
+               if (!pmdp)
+                       return -ENOMEM;
+               new_pgd = pfn_pgd(PFN_DOWN(__pa(pmdp)), __pgprot(_PAGE_TABLE));
+               rc = stage2_set_pgd(kvm, addr, &new_pgd);
+               if (rc)
+                       return rc;
+       }
+
+       if (pgd_val(*pgdp) & _PAGE_LEAF)
+               return -EEXIST;
+
+       pmdp = (void *)pgd_page_vaddr(*pgdp);
+       pmdp = &pmdp[pmd_index(addr)];
+
+       *pmdp = *new_pmd;
+       if (pmd_val(*pmdp) & _PAGE_LEAF)
+               stage2_remote_tlb_flush(kvm, addr);
+
+       return 0;
+}
+
+static int stage2_set_pte(struct kvm *kvm,
+                         struct kvm_mmu_page_cache *pcache,
+                         gpa_t addr, const pte_t *new_pte)
+{
+       int rc;
+       pte_t *ptep;
+       pmd_t new_pmd;
+       pmd_t *pmdp;
+       pgd_t new_pgd;
+       pgd_t *pgdp = &kvm->arch.pgd[pgd_index(addr)];
+
+       if (!pgd_val(*pgdp)) {
+               pmdp = stage2_cache_alloc(pcache);
+               if (!pmdp)
+                       return -ENOMEM;
+               new_pgd = pfn_pgd(PFN_DOWN(__pa(pmdp)), __pgprot(_PAGE_TABLE));
+               rc = stage2_set_pgd(kvm, addr, &new_pgd);
+               if (rc)
+                       return rc;
+       }
+
+       if (pgd_val(*pgdp) & _PAGE_LEAF)
+               return -EEXIST;
+
+       if (stage2_have_pmd) {
+               pmdp = (void *)pgd_page_vaddr(*pgdp);
+               pmdp = &pmdp[pmd_index(addr)];
+               if (!pmd_present(*pmdp)) {
+                       ptep = stage2_cache_alloc(pcache);
+                       if (!ptep)
+                               return -ENOMEM;
+                       new_pmd = pfn_pmd(PFN_DOWN(__pa(ptep)),
+                                         __pgprot(_PAGE_TABLE));
+                       rc = stage2_set_pmd(kvm, pcache, addr, &new_pmd);
+                       if (rc)
+                               return rc;
+               }
+
+               if (pmd_val(*pmdp) & _PAGE_LEAF)
+                       return -EEXIST;
+
+               ptep = (void *)pmd_page_vaddr(*pmdp);
+       } else {
+               ptep = (void *)pgd_page_vaddr(*pgdp);
+       }
+
+       ptep = &ptep[pte_index(addr)];
+
+       *ptep = *new_pte;
+       if (pte_val(*ptep) & _PAGE_LEAF)
+               stage2_remote_tlb_flush(kvm, addr);
+
+       return 0;
+}
+
+static int stage2_map_page(struct kvm *kvm,
+                          struct kvm_mmu_page_cache *pcache,
+                          gpa_t gpa, phys_addr_t hpa,
+                          unsigned long page_size, pgprot_t prot)
+{
+       pte_t new_pte;
+       pmd_t new_pmd;
+       pgd_t new_pgd;
+
+       if (page_size == PAGE_SIZE) {
+               new_pte = pfn_pte(PFN_DOWN(hpa), prot);
+               return stage2_set_pte(kvm, pcache, gpa, &new_pte);
+       }
+
+       if (stage2_have_pmd && page_size == PMD_SIZE) {
+               new_pmd = pfn_pmd(PFN_DOWN(hpa), prot);
+               return stage2_set_pmd(kvm, pcache, gpa, &new_pmd);
+       }
+
+       if (page_size == PGDIR_SIZE) {
+               new_pgd = pfn_pgd(PFN_DOWN(hpa), prot);
+               return stage2_set_pgd(kvm, gpa, &new_pgd);
+       }
+
+       return -EINVAL;
+}
+
+enum stage2_op {
+       STAGE2_OP_NOP = 0,      /* Nothing */
+       STAGE2_OP_CLEAR,        /* Clear/Unmap */
+       STAGE2_OP_WP,           /* Write-protect */
+};
+
+static void stage2_op_pte(struct kvm *kvm, gpa_t addr, pte_t *ptep,
+                         enum stage2_op op)
+{
+       BUG_ON(addr & (PAGE_SIZE - 1));
+
+       if (!pte_present(*ptep))
+               return;
+
+       if (op == STAGE2_OP_CLEAR)
+               set_pte(ptep, __pte(0));
+       else if (op == STAGE2_OP_WP)
+               set_pte(ptep, __pte(pte_val(*ptep) & ~_PAGE_WRITE));
+       stage2_remote_tlb_flush(kvm, addr);
+}
+
+static void stage2_op_pmd(struct kvm *kvm, gpa_t addr, pmd_t *pmdp,
+                         enum stage2_op op)
+{
+       int i;
+       pte_t *ptep;
+
+       BUG_ON(addr & (PMD_SIZE - 1));
+
+       if (!pmd_present(*pmdp))
+               return;
+
+       if (pmd_val(*pmdp) & _PAGE_LEAF)
+               ptep = NULL;
+       else
+               ptep = (pte_t *)pmd_page_vaddr(*pmdp);
+
+       if (op == STAGE2_OP_CLEAR)
+               set_pmd(pmdp, __pmd(0));
+
+       if (ptep) {
+               for (i = 0; i < PTRS_PER_PTE; i++)
+                       stage2_op_pte(kvm, addr + i * PAGE_SIZE, &ptep[i], op);
+               if (op == STAGE2_OP_CLEAR)
+                       put_page(virt_to_page(ptep));
+       } else {
+               if (op == STAGE2_OP_WP)
+                       set_pmd(pmdp, __pmd(pmd_val(*pmdp) & ~_PAGE_WRITE));
+               stage2_remote_tlb_flush(kvm, addr);
+       }
+}
+
+static void stage2_op_pgd(struct kvm *kvm, gpa_t addr, pgd_t *pgdp,
+                         enum stage2_op op)
+{
+       int i;
+       pte_t *ptep;
+       pmd_t *pmdp;
+
+       BUG_ON(addr & (PGDIR_SIZE - 1));
+
+       if (!pgd_val(*pgdp))
+               return;
+
+       ptep = NULL;
+       pmdp = NULL;
+       if (!(pgd_val(*pgdp) & _PAGE_LEAF)) {
+               if (stage2_have_pmd)
+                       pmdp = (pmd_t *)pgd_page_vaddr(*pgdp);
+               else
+                       ptep = (pte_t *)pgd_page_vaddr(*pgdp);
+       }
+
+       if (op == STAGE2_OP_CLEAR)
+               set_pgd(pgdp, __pgd(0));
+
+       if (pmdp) {
+               for (i = 0; i < PTRS_PER_PMD; i++)
+                       stage2_op_pmd(kvm, addr + i * PMD_SIZE, &pmdp[i], op);
+               if (op == STAGE2_OP_CLEAR)
+                       put_page(virt_to_page(pmdp));
+       } else if (ptep) {
+               for (i = 0; i < PTRS_PER_PTE; i++)
+                       stage2_op_pte(kvm, addr + i * PAGE_SIZE, &ptep[i], op);
+               if (op == STAGE2_OP_CLEAR)
+                       put_page(virt_to_page(ptep));
+       } else {
+               if (op == STAGE2_OP_WP)
+                       set_pgd(pgdp, __pgd(pgd_val(*pgdp) & ~_PAGE_WRITE));
+               stage2_remote_tlb_flush(kvm, addr);
+       }
+}
+
+static void stage2_unmap_range(struct kvm *kvm, gpa_t start, gpa_t size)
+{
+       pmd_t *pmdp;
+       pte_t *ptep;
+       pgd_t *pgdp;
+       gpa_t addr = start, end = start + size;
+
+       while (addr < end) {
+               pgdp = &kvm->arch.pgd[pgd_index(addr)];
+               if (!pgd_val(*pgdp)) {
+                       addr += PGDIR_SIZE;
+                       continue;
+               } else if (!(addr & (PGDIR_SIZE - 1)) &&
+                         ((end - addr) >= PGDIR_SIZE)) {
+                       stage2_op_pgd(kvm, addr, pgdp, STAGE2_OP_CLEAR);
+                       addr += PGDIR_SIZE;
+                       continue;
+               }
+
+               if (stage2_have_pmd) {
+                       pmdp = (pmd_t *)pgd_page_vaddr(*pgdp);
+                       if (!pmd_present(*pmdp)) {
+                               addr += PMD_SIZE;
+                               continue;
+                       } else if (!(addr & (PMD_SIZE - 1)) &&
+                                  ((end - addr) >= PMD_SIZE)) {
+                               stage2_op_pmd(kvm, addr, pmdp,
+                                             STAGE2_OP_CLEAR);
+                               addr += PMD_SIZE;
+                               continue;
+                       }
+                       ptep = (pte_t *)pmd_page_vaddr(*pmdp);
+               } else {
+                       ptep = (pte_t *)pgd_page_vaddr(*pgdp);
+               }
+
+               stage2_op_pte(kvm, addr, ptep, STAGE2_OP_CLEAR);
+               addr += PAGE_SIZE;
+       }
+}
+
+static void stage2_wp_range(struct kvm *kvm, gpa_t start, gpa_t end)
+{
+       pmd_t *pmdp;
+       pte_t *ptep;
+       pgd_t *pgdp;
+       gpa_t addr = start;
+
+       while (addr < end) {
+               pgdp = &kvm->arch.pgd[pgd_index(addr)];
+               if (!pgd_val(*pgdp)) {
+                       addr += PGDIR_SIZE;
+                       continue;
+               } else if (!(addr & (PGDIR_SIZE - 1)) &&
+                          ((end - addr) >= PGDIR_SIZE)) {
+                       stage2_op_pgd(kvm, addr, pgdp, STAGE2_OP_WP);
+                       addr += PGDIR_SIZE;
+                       continue;
+               }
+
+               if (stage2_have_pmd) {
+                       pmdp = (pmd_t *)pgd_page_vaddr(*pgdp);
+                       if (!pmd_present(*pmdp)) {
+                               addr += PMD_SIZE;
+                               continue;
+                       } else if (!(addr & (PMD_SIZE - 1)) &&
+                                  ((end - addr) >= PMD_SIZE)) {
+                               stage2_op_pmd(kvm, addr, pmdp, STAGE2_OP_WP);
+                               addr += PMD_SIZE;
+                               continue;
+                       }
+                       ptep = (pte_t *)pmd_page_vaddr(*pmdp);
+               } else {
+                       ptep = (pte_t *)pgd_page_vaddr(*pgdp);
+               }
+
+               stage2_op_pte(kvm, addr, ptep, STAGE2_OP_WP);
+               addr += PAGE_SIZE;
+       }
+}
+
+void stage2_wp_memory_region(struct kvm *kvm, int slot)
+{
+       struct kvm_memslots *slots = kvm_memslots(kvm);
+       struct kvm_memory_slot *memslot = id_to_memslot(slots, slot);
+       phys_addr_t start = memslot->base_gfn << PAGE_SHIFT;
+       phys_addr_t end = (memslot->base_gfn + memslot->npages) << PAGE_SHIFT;
+
+       spin_lock(&kvm->mmu_lock);
+       stage2_wp_range(kvm, start, end);
+       spin_unlock(&kvm->mmu_lock);
+       kvm_flush_remote_tlbs(kvm);
+}
+
+int stage2_ioremap(struct kvm *kvm, gpa_t gpa, phys_addr_t hpa,
+                  unsigned long size, bool writable)
+{
+       pte_t pte;
+       int ret = 0;
+       unsigned long pfn;
+       phys_addr_t addr, end;
+       struct kvm_mmu_page_cache pcache = { 0, };
+
+       end = (gpa + size + PAGE_SIZE - 1) & PAGE_MASK;
+       pfn = __phys_to_pfn(hpa);
+
+       for (addr = gpa; addr < end; addr += PAGE_SIZE) {
+               pte = pfn_pte(pfn, PAGE_KERNEL);
+
+               if (!writable)
+                       pte = pte_wrprotect(pte);
+
+               ret = stage2_cache_topup(&pcache,
+                                        stage2_cache_min_pages,
+                                        KVM_MMU_PAGE_CACHE_NR_OBJS);
+               if (ret)
+                       goto out;
+
+               spin_lock(&kvm->mmu_lock);
+               ret = stage2_set_pte(kvm, &pcache, addr, &pte);
+               spin_unlock(&kvm->mmu_lock);
+               if (ret)
+                       goto out;
+
+               pfn++;
+       }
+
+out:
+       stage2_cache_flush(&pcache);
+       return ret;
+
+}
+
 void kvm_arch_free_memslot(struct kvm *kvm, struct kvm_memory_slot *free,
                           struct kvm_memory_slot *dont)
 {
@@ -35,7 +467,7 @@ void kvm_arch_memslots_updated(struct kvm *kvm, u64 gen)
 
 void kvm_arch_flush_shadow_all(struct kvm *kvm)
 {
-       /* TODO: */
+       kvm_riscv_stage2_free_pgd(kvm);
 }
 
 void kvm_arch_flush_shadow_memslot(struct kvm *kvm,
@@ -49,7 +481,13 @@ void kvm_arch_commit_memory_region(struct kvm *kvm,
                                const struct kvm_memory_slot *new,
                                enum kvm_mr_change change)
 {
-       /* TODO: */
+       /*
+        * At this point memslot has been committed and there is an
+        * allocated dirty_bitmap[], dirty pages will be be tracked while the
+        * memory slot is write protected.
+        */
+       if (change != KVM_MR_DELETE && mem->flags & KVM_MEM_LOG_DIRTY_PAGES)
+               stage2_wp_memory_region(kvm, mem->slot);
 }
 
 int kvm_arch_prepare_memory_region(struct kvm *kvm,
@@ -57,34 +495,219 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
                                const struct kvm_userspace_memory_region *mem,
                                enum kvm_mr_change change)
 {
-       /* TODO: */
-       return 0;
+       hva_t hva = mem->userspace_addr;
+       hva_t reg_end = hva + mem->memory_size;
+       bool writable = !(mem->flags & KVM_MEM_READONLY);
+       int ret = 0;
+
+       if (change != KVM_MR_CREATE && change != KVM_MR_MOVE &&
+                       change != KVM_MR_FLAGS_ONLY)
+               return 0;
+
+       /*
+        * Prevent userspace from creating a memory region outside of the GPA
+        * space addressable by the KVM guest GPA space.
+        */
+       if ((memslot->base_gfn + memslot->npages) >=
+           (stage2_gpa_size >> PAGE_SHIFT))
+               return -EFAULT;
+
+       down_read(&current->mm->mmap_sem);
+
+       /*
+        * A memory region could potentially cover multiple VMAs, and
+        * any holes between them, so iterate over all of them to find
+        * out if we can map any of them right now.
+        *
+        *     +--------------------------------------------+
+        * +---------------+----------------+   +----------------+
+        * |   : VMA 1     |      VMA 2     |   |    VMA 3  :    |
+        * +---------------+----------------+   +----------------+
+        *     |               memory region                |
+        *     +--------------------------------------------+
+        */
+       do {
+               struct vm_area_struct *vma = find_vma(current->mm, hva);
+               hva_t vm_start, vm_end;
+
+               if (!vma || vma->vm_start >= reg_end)
+                       break;
+
+               /*
+                * Mapping a read-only VMA is only allowed if the
+                * memory region is configured as read-only.
+                */
+               if (writable && !(vma->vm_flags & VM_WRITE)) {
+                       ret = -EPERM;
+                       break;
+               }
+
+               /* Take the intersection of this VMA with the memory region */
+               vm_start = max(hva, vma->vm_start);
+               vm_end = min(reg_end, vma->vm_end);
+
+               if (vma->vm_flags & VM_PFNMAP) {
+                       gpa_t gpa = mem->guest_phys_addr +
+                                   (vm_start - mem->userspace_addr);
+                       phys_addr_t pa;
+
+                       pa = (phys_addr_t)vma->vm_pgoff << PAGE_SHIFT;
+                       pa += vm_start - vma->vm_start;
+
+                       /* IO region dirty page logging not allowed */
+                       if (memslot->flags & KVM_MEM_LOG_DIRTY_PAGES) {
+                               ret = -EINVAL;
+                               goto out;
+                       }
+
+                       ret = stage2_ioremap(kvm, gpa, pa,
+                                            vm_end - vm_start, writable);
+                       if (ret)
+                               break;
+               }
+               hva = vm_end;
+       } while (hva < reg_end);
+
+       if (change == KVM_MR_FLAGS_ONLY)
+               goto out;
+
+       spin_lock(&kvm->mmu_lock);
+       if (ret)
+               stage2_unmap_range(kvm, mem->guest_phys_addr,
+                                  mem->memory_size);
+       spin_unlock(&kvm->mmu_lock);
+
+out:
+       up_read(&current->mm->mmap_sem);
+       return ret;
 }
 
 int kvm_riscv_stage2_map(struct kvm_vcpu *vcpu, gpa_t gpa, unsigned long hva,
                         bool is_write)
 {
-       /* TODO: */
-       return 0;
+       int ret;
+       short lsb;
+       kvm_pfn_t hfn;
+       bool writeable;
+       gfn_t gfn = gpa >> PAGE_SHIFT;
+       struct vm_area_struct *vma;
+       struct kvm *kvm = vcpu->kvm;
+       struct kvm_mmu_page_cache *pcache = &vcpu->arch.mmu_page_cache;
+       unsigned long vma_pagesize;
+
+       down_read(&current->mm->mmap_sem);
+
+       vma = find_vma_intersection(current->mm, hva, hva + 1);
+       if (unlikely(!vma)) {
+               kvm_err("Failed to find VMA for hva 0x%lx\n", hva);
+               up_read(&current->mm->mmap_sem);
+               return -EFAULT;
+       }
+
+       vma_pagesize = vma_kernel_pagesize(vma);
+
+       up_read(&current->mm->mmap_sem);
+
+       if (vma_pagesize != PGDIR_SIZE &&
+           vma_pagesize != PMD_SIZE &&
+           vma_pagesize != PAGE_SIZE) {
+               kvm_err("Invalid VMA page size 0x%lx\n", vma_pagesize);
+               return -EFAULT;
+       }
+
+       /* We need minimum second+third level pages */
+       ret = stage2_cache_topup(pcache, stage2_cache_min_pages,
+                                KVM_MMU_PAGE_CACHE_NR_OBJS);
+       if (ret) {
+               kvm_err("Failed to topup stage2 cache\n");
+               return ret;
+       }
+
+       hfn = gfn_to_pfn_prot(kvm, gfn, is_write, &writeable);
+       if (hfn == KVM_PFN_ERR_HWPOISON) {
+               if (is_vm_hugetlb_page(vma))
+                       lsb = huge_page_shift(hstate_vma(vma));
+               else
+                       lsb = PAGE_SHIFT;
+
+               send_sig_mceerr(BUS_MCEERR_AR, (void __user *)hva,
+                               lsb, current);
+               return 0;
+       }
+       if (is_error_noslot_pfn(hfn))
+               return -EFAULT;
+       if (!writeable && is_write)
+               return -EPERM;
+
+       spin_lock(&kvm->mmu_lock);
+
+       if (writeable) {
+               kvm_set_pfn_dirty(hfn);
+               ret = stage2_map_page(kvm, pcache, gpa, hfn << PAGE_SHIFT,
+                                     vma_pagesize, PAGE_WRITE_EXEC);
+       } else {
+               ret = stage2_map_page(kvm, pcache, gpa, hfn << PAGE_SHIFT,
+                                     vma_pagesize, PAGE_READ_EXEC);
+       }
+
+       if (ret)
+               kvm_err("Failed to map in stage2\n");
+
+       spin_unlock(&kvm->mmu_lock);
+       kvm_set_pfn_accessed(hfn);
+       kvm_release_pfn_clean(hfn);
+       return ret;
 }
 
 void kvm_riscv_stage2_flush_cache(struct kvm_vcpu *vcpu)
 {
-       /* TODO: */
+       stage2_cache_flush(&vcpu->arch.mmu_page_cache);
 }
 
 int kvm_riscv_stage2_alloc_pgd(struct kvm *kvm)
 {
-       /* TODO: */
+       if (kvm->arch.pgd != NULL) {
+               kvm_err("kvm_arch already initialized?\n");
+               return -EINVAL;
+       }
+
+       kvm->arch.pgd = alloc_pages_exact(PAGE_SIZE, GFP_KERNEL | __GFP_ZERO);
+       if (!kvm->arch.pgd)
+               return -ENOMEM;
+       kvm->arch.pgd_phys = virt_to_phys(kvm->arch.pgd);
+
        return 0;
 }
 
 void kvm_riscv_stage2_free_pgd(struct kvm *kvm)
 {
-       /* TODO: */
+       void *pgd = NULL;
+
+       spin_lock(&kvm->mmu_lock);
+       if (kvm->arch.pgd) {
+               stage2_unmap_range(kvm, 0UL, stage2_gpa_size);
+               pgd = READ_ONCE(kvm->arch.pgd);
+               kvm->arch.pgd = NULL;
+               kvm->arch.pgd_phys = 0;
+       }
+       spin_unlock(&kvm->mmu_lock);
+
+       /* Free the HW pgd, one page at a time */
+       if (pgd)
+               free_pages_exact(pgd, PAGE_SIZE);
 }
 
 void kvm_riscv_stage2_update_hgatp(struct kvm_vcpu *vcpu)
 {
-       /* TODO: */
+       unsigned long hgatp = HGATP_MODE;
+       struct kvm_arch *k = &vcpu->kvm->arch;
+
+       hgatp |= (READ_ONCE(k->vmid.vmid) << HGATP_VMID_SHIFT) &
+                HGATP_VMID_MASK;
+       hgatp |= (k->pgd_phys >> PAGE_SHIFT) & HGATP_PPN;
+
+       csr_write(CSR_HGATP, hgatp);
+
+       if (!kvm_riscv_stage2_vmid_bits())
+               __kvm_riscv_hfence_gvma_all();
 }
-- 
2.17.1

Reply via email to