KVM: arm: Add initial dirty page locking support
authorMario Smarduch <m.smarduch@samsung.com>
Thu, 15 Jan 2015 23:58:56 +0000 (15:58 -0800)
committerChristoffer Dall <christoffer.dall@linaro.org>
Fri, 16 Jan 2015 13:40:14 +0000 (14:40 +0100)
Add support for initial write protection of VM memslots. This patch
series assumes that huge PUDs will not be used in 2nd stage tables, which is
always valid on ARMv7

Acked-by: Christoffer Dall <christoffer.dall@linaro.org>
Signed-off-by: Mario Smarduch <m.smarduch@samsung.com>
arch/arm/include/asm/kvm_host.h
arch/arm/include/asm/kvm_mmu.h
arch/arm/include/asm/pgtable-3level.h
arch/arm/kvm/mmu.c

index acfced36986bcc7fa83de7ebe74ce94b5a6f31ef..38931d4f4ba3ffe0a2e75dbf27d679d71f4db6bd 100644 (file)
@@ -246,6 +246,8 @@ static inline void vgic_arch_setup(const struct vgic_params *vgic)
 int kvm_perf_init(void);
 int kvm_perf_teardown(void);
 
+void kvm_mmu_wp_memory_region(struct kvm *kvm, int slot);
+
 static inline void kvm_arch_hardware_disable(void) {}
 static inline void kvm_arch_hardware_unsetup(void) {}
 static inline void kvm_arch_sync_events(struct kvm *kvm) {}
index 63e0ecc0490180e8b8b5e548b6b6a17b95b725e1..2672cf84afd16a59b7172840b65c1a7eb31d901c 100644 (file)
@@ -114,6 +114,27 @@ static inline void kvm_set_s2pmd_writable(pmd_t *pmd)
        pmd_val(*pmd) |= L_PMD_S2_RDWR;
 }
 
+static inline void kvm_set_s2pte_readonly(pte_t *pte)
+{
+       pte_val(*pte) = (pte_val(*pte) & ~L_PTE_S2_RDWR) | L_PTE_S2_RDONLY;
+}
+
+static inline bool kvm_s2pte_readonly(pte_t *pte)
+{
+       return (pte_val(*pte) & L_PTE_S2_RDWR) == L_PTE_S2_RDONLY;
+}
+
+static inline void kvm_set_s2pmd_readonly(pmd_t *pmd)
+{
+       pmd_val(*pmd) = (pmd_val(*pmd) & ~L_PMD_S2_RDWR) | L_PMD_S2_RDONLY;
+}
+
+static inline bool kvm_s2pmd_readonly(pmd_t *pmd)
+{
+       return (pmd_val(*pmd) & L_PMD_S2_RDWR) == L_PMD_S2_RDONLY;
+}
+
+
 /* Open coded p*d_addr_end that can deal with 64bit addresses */
 #define kvm_pgd_addr_end(addr, end)                                    \
 ({     u64 __boundary = ((addr) + PGDIR_SIZE) & PGDIR_MASK;            \
index a31ecdad4b5966dbf15953054bcc0c9d260cd302..ae1d30a1aaaeadd3c99957e11b6bc04eb864d6d6 100644 (file)
 #define L_PTE_S2_RDONLY                        (_AT(pteval_t, 1) << 6)   /* HAP[1]   */
 #define L_PTE_S2_RDWR                  (_AT(pteval_t, 3) << 6)   /* HAP[2:1] */
 
+#define L_PMD_S2_RDONLY                        (_AT(pmdval_t, 1) << 6)   /* HAP[1]   */
 #define L_PMD_S2_RDWR                  (_AT(pmdval_t, 3) << 6)   /* HAP[2:1] */
 
 /*
index 1dc9778a00af358431bbed021ba2d5244642debb..c9e5f384832ed0316ed5781c7cf8e249d6d8c122 100644 (file)
@@ -45,6 +45,7 @@ static phys_addr_t hyp_idmap_vector;
 #define hyp_pgd_order get_order(PTRS_PER_PGD * sizeof(pgd_t))
 
 #define kvm_pmd_huge(_x)       (pmd_huge(_x) || pmd_trans_huge(_x))
+#define kvm_pud_huge(_x)       pud_huge(_x)
 
 static void kvm_tlb_flush_vmid_ipa(struct kvm *kvm, phys_addr_t ipa)
 {
@@ -905,6 +906,131 @@ static bool kvm_is_device_pfn(unsigned long pfn)
        return !pfn_valid(pfn);
 }
 
+#ifdef CONFIG_ARM
+/**
+ * stage2_wp_ptes - write protect PMD range
+ * @pmd:       pointer to pmd entry
+ * @addr:      range start address
+ * @end:       range end address
+ */
+static void stage2_wp_ptes(pmd_t *pmd, phys_addr_t addr, phys_addr_t end)
+{
+       pte_t *pte;
+
+       pte = pte_offset_kernel(pmd, addr);
+       do {
+               if (!pte_none(*pte)) {
+                       if (!kvm_s2pte_readonly(pte))
+                               kvm_set_s2pte_readonly(pte);
+               }
+       } while (pte++, addr += PAGE_SIZE, addr != end);
+}
+
+/**
+ * stage2_wp_pmds - write protect PUD range
+ * @pud:       pointer to pud entry
+ * @addr:      range start address
+ * @end:       range end address
+ */
+static void stage2_wp_pmds(pud_t *pud, phys_addr_t addr, phys_addr_t end)
+{
+       pmd_t *pmd;
+       phys_addr_t next;
+
+       pmd = pmd_offset(pud, addr);
+
+       do {
+               next = kvm_pmd_addr_end(addr, end);
+               if (!pmd_none(*pmd)) {
+                       if (kvm_pmd_huge(*pmd)) {
+                               if (!kvm_s2pmd_readonly(pmd))
+                                       kvm_set_s2pmd_readonly(pmd);
+                       } else {
+                               stage2_wp_ptes(pmd, addr, next);
+                       }
+               }
+       } while (pmd++, addr = next, addr != end);
+}
+
+/**
+  * stage2_wp_puds - write protect PGD range
+  * @pgd:      pointer to pgd entry
+  * @addr:     range start address
+  * @end:      range end address
+  *
+  * Process PUD entries, for a huge PUD we cause a panic.
+  */
+static void  stage2_wp_puds(pgd_t *pgd, phys_addr_t addr, phys_addr_t end)
+{
+       pud_t *pud;
+       phys_addr_t next;
+
+       pud = pud_offset(pgd, addr);
+       do {
+               next = kvm_pud_addr_end(addr, end);
+               if (!pud_none(*pud)) {
+                       /* TODO:PUD not supported, revisit later if supported */
+                       BUG_ON(kvm_pud_huge(*pud));
+                       stage2_wp_pmds(pud, addr, next);
+               }
+       } while (pud++, addr = next, addr != end);
+}
+
+/**
+ * stage2_wp_range() - write protect stage2 memory region range
+ * @kvm:       The KVM pointer
+ * @addr:      Start address of range
+ * @end:       End address of range
+ */
+static void stage2_wp_range(struct kvm *kvm, phys_addr_t addr, phys_addr_t end)
+{
+       pgd_t *pgd;
+       phys_addr_t next;
+
+       pgd = kvm->arch.pgd + pgd_index(addr);
+       do {
+               /*
+                * Release kvm_mmu_lock periodically if the memory region is
+                * large. Otherwise, we may see kernel panics with
+                * CONFIG_DETECT_HUNG_TASK, CONFIG_LOCK_DETECTOR,
+                * CONFIG_LOCK_DEP. Additionally, holding the lock too long
+                * will also starve other vCPUs.
+                */
+               if (need_resched() || spin_needbreak(&kvm->mmu_lock))
+                       cond_resched_lock(&kvm->mmu_lock);
+
+               next = kvm_pgd_addr_end(addr, end);
+               if (pgd_present(*pgd))
+                       stage2_wp_puds(pgd, addr, next);
+       } while (pgd++, addr = next, addr != end);
+}
+
+/**
+ * kvm_mmu_wp_memory_region() - write protect stage 2 entries for memory slot
+ * @kvm:       The KVM pointer
+ * @slot:      The memory slot to write protect
+ *
+ * Called to start logging dirty pages after memory region
+ * KVM_MEM_LOG_DIRTY_PAGES operation is called. After this function returns
+ * all present PMD and PTEs are write protected in the memory region.
+ * Afterwards read of dirty page log can be called.
+ *
+ * Acquires kvm_mmu_lock. Called with kvm->slots_lock mutex acquired,
+ * serializing operations for VM memory regions.
+ */
+void kvm_mmu_wp_memory_region(struct kvm *kvm, int slot)
+{
+       struct kvm_memory_slot *memslot = id_to_memslot(kvm->memslots, 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);
+}
+#endif
+
 static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
                          struct kvm_memory_slot *memslot, unsigned long hva,
                          unsigned long fault_status)
@@ -1292,6 +1418,15 @@ void kvm_arch_commit_memory_region(struct kvm *kvm,
                                   const struct kvm_memory_slot *old,
                                   enum kvm_mr_change change)
 {
+#ifdef CONFIG_ARM
+       /*
+        * 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)
+               kvm_mmu_wp_memory_region(kvm, mem->slot);
+#endif
 }
 
 int kvm_arch_prepare_memory_region(struct kvm *kvm,