summaryrefslogtreecommitdiff
path: root/arch/riscv/kvm/mmu.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/riscv/kvm/mmu.c')
-rw-r--r--arch/riscv/kvm/mmu.c108
1 files changed, 99 insertions, 9 deletions
diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
index 8a0aa5e0e216..6035b5ec9503 100644
--- a/arch/riscv/kvm/mmu.c
+++ b/arch/riscv/kvm/mmu.c
@@ -16,6 +16,9 @@
#include <asm/kvm_mmu.h>
#include <asm/kvm_nacl.h>
+static bool __read_mostly eager_page_split = true;
+module_param(eager_page_split, bool, 0644);
+
static void mmu_wp_memory_region(struct kvm *kvm, int slot)
{
struct kvm_memslots *slots = kvm_memslots(kvm);
@@ -98,6 +101,62 @@ void kvm_riscv_mmu_iounmap(struct kvm *kvm, gpa_t gpa, unsigned long size)
size >> PAGE_SHIFT);
}
+static bool need_topup_split_caches_or_resched(struct kvm *kvm, int count)
+{
+ struct kvm_mmu_memory_cache *cache;
+
+ if (need_resched() || rwlock_needbreak(&kvm->mmu_lock))
+ return true;
+
+ cache = &kvm->arch.pgd_split_page_cache;
+ return kvm_mmu_memory_cache_nr_free_objects(cache) < count;
+}
+
+static bool mmu_split_huge_pages(struct kvm_gstage *gstage,
+ phys_addr_t start, phys_addr_t end)
+{
+ struct kvm *kvm = gstage->kvm;
+ struct kvm_mmu_memory_cache *pcache = &kvm->arch.pgd_split_page_cache;
+ phys_addr_t addr = ALIGN_DOWN(start, PMD_SIZE);
+ phys_addr_t last_flush_gfn = addr >> PAGE_SHIFT;
+ int count = gstage->pgd_levels;
+ bool flush = false;
+ int ret;
+
+ lockdep_assert_held_write(&kvm->mmu_lock);
+
+ while (addr < end) {
+ if (need_topup_split_caches_or_resched(kvm, count)) {
+ if (flush) {
+ kvm_flush_remote_tlbs_range(kvm, last_flush_gfn,
+ (addr >> PAGE_SHIFT) - last_flush_gfn);
+ last_flush_gfn = addr >> PAGE_SHIFT;
+ flush = false;
+ }
+
+ write_unlock(&kvm->mmu_lock);
+ cond_resched();
+
+ ret = kvm_mmu_topup_memory_cache(pcache, count);
+ if (ret) {
+ kvm_err("Failed to toup split page cache\n");
+ write_lock(&kvm->mmu_lock);
+ return flush;
+ }
+ write_lock(&kvm->mmu_lock);
+ }
+
+ if (!kvm->arch.pgd)
+ return flush;
+
+ flush |= kvm_riscv_gstage_split_huge(gstage, pcache, addr, 0, false);
+
+ addr += PMD_SIZE;
+ }
+
+ return flush;
+}
+
void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,
struct kvm_memory_slot *slot,
gfn_t gfn_offset,
@@ -107,14 +166,20 @@ void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,
phys_addr_t start = (base_gfn + __ffs(mask)) << PAGE_SHIFT;
phys_addr_t end = (base_gfn + __fls(mask) + 1) << PAGE_SHIFT;
struct kvm_gstage gstage;
- bool flush;
kvm_riscv_gstage_init(&gstage, kvm);
- flush = kvm_riscv_gstage_wp_range(&gstage, start, end);
- if (flush)
- kvm_flush_remote_tlbs_range(kvm, start >> PAGE_SHIFT,
- (end - start) >> PAGE_SHIFT);
+ kvm_riscv_gstage_wp_pt_masked(&gstage, base_gfn, mask);
+
+ if (kvm_dirty_log_manual_protect_and_init_set(kvm)) {
+ if (READ_ONCE(eager_page_split))
+ mmu_split_huge_pages(&gstage, start, end);
+ }
+
+ /*
+ * Remote TLB flush is not needed here since callers of
+ * kvm_arch_mmu_enable_log_dirty_pt_masked() already do it.
+ */
}
void kvm_arch_sync_dirty_log(struct kvm *kvm, struct kvm_memory_slot *memslot)
@@ -152,6 +217,25 @@ void kvm_arch_flush_shadow_memslot(struct kvm *kvm,
size >> PAGE_SHIFT);
}
+static void mmu_split_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;
+ struct kvm_gstage gstage;
+ bool flush;
+
+ kvm_riscv_gstage_init(&gstage, kvm);
+
+ write_lock(&kvm->mmu_lock);
+ flush = mmu_split_huge_pages(&gstage, start, end);
+ write_unlock(&kvm->mmu_lock);
+
+ if (flush)
+ kvm_flush_remote_tlbs_memslot(kvm, memslot);
+}
+
void kvm_arch_commit_memory_region(struct kvm *kvm,
struct kvm_memory_slot *old,
const struct kvm_memory_slot *new,
@@ -165,6 +249,9 @@ void kvm_arch_commit_memory_region(struct kvm *kvm,
if (kvm_dirty_log_manual_protect_and_init_set(kvm))
return;
mmu_wp_memory_region(kvm, new->id);
+
+ if (READ_ONCE(eager_page_split))
+ mmu_split_memory_region(kvm, new->id);
}
}
@@ -185,7 +272,7 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
* Prevent userspace from creating a memory region outside of the GPA
* space addressable by the KVM guest GPA space.
*/
- if ((new->base_gfn + new->npages) >=
+ if ((new->base_gfn + new->npages) >
kvm_riscv_gstage_gpa_size(kvm->arch.pgd_levels) >> PAGE_SHIFT)
return -EFAULT;
@@ -541,7 +628,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
kvm_pfn_t hfn;
bool is_hugetlb;
bool writable;
- short vma_pageshift;
+ unsigned int vma_pageshift;
gfn_t gfn = gpa >> PAGE_SHIFT;
struct vm_area_struct *vma;
struct kvm *kvm = vcpu->kvm;
@@ -670,13 +757,14 @@ int kvm_riscv_mmu_alloc_pgd(struct kvm *kvm)
return -EINVAL;
}
- pgd_page = alloc_pages(GFP_KERNEL | __GFP_ZERO,
- get_order(kvm_riscv_gstage_pgd_size));
+ pgd_page = alloc_pages(GFP_KERNEL_ACCOUNT | __GFP_ZERO,
+ get_order(kvm_riscv_gstage_pgd_size));
if (!pgd_page)
return -ENOMEM;
kvm->arch.pgd = page_to_virt(pgd_page);
kvm->arch.pgd_phys = page_to_phys(pgd_page);
kvm->arch.pgd_levels = kvm_riscv_gstage_max_pgd_levels;
+ kvm->arch.pgd_split_page_cache.gfp_zero = __GFP_ZERO;
return 0;
}
@@ -704,6 +792,8 @@ void kvm_riscv_mmu_free_pgd(struct kvm *kvm)
if (pgd)
free_pages((unsigned long)pgd, get_order(kvm_riscv_gstage_pgd_size));
+
+ kvm_mmu_free_memory_cache(&kvm->arch.pgd_split_page_cache);
}
void kvm_riscv_mmu_update_hgatp(struct kvm_vcpu *vcpu)