diff mbox

[kvm-unit-tests,29/32] lib: x86: page table utilities

Message ID 20170421005004.137260-30-dmatlack@google.com (mailing list archive)
State New, archived
Headers show

Commit Message

David Matlack April 21, 2017, 12:50 a.m. UTC
From: Peter Feiner <pfeiner@google.com>

Convenience functions for manipulating and querying page tables.

Signed-off-by: Peter Feiner <pfeiner@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
---
 lib/x86/vm.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++-----------
 lib/x86/vm.h | 27 +++++++++++++++++
 2 files changed, 108 insertions(+), 18 deletions(-)
diff mbox

Patch

diff --git a/lib/x86/vm.c b/lib/x86/vm.c
index 438ab9c70ffa..d52bb53fcf5e 100644
--- a/lib/x86/vm.c
+++ b/lib/x86/vm.c
@@ -139,23 +139,62 @@  unsigned long *install_pte(unsigned long *cr3,
     return &pt[offset];
 }
 
+/*
+ * Finds last PTE in the mapping of @virt that's at or above @lowest_level. The
+ * returned PTE isn't necessarily present, but its parent is.
+ */
+struct pte_search find_pte_level(unsigned long *cr3, void *virt,
+				 int lowest_level)
+{
+	unsigned long *pt = cr3, pte;
+	unsigned offset;
+	unsigned long shift;
+	struct pte_search r;
+
+	assert(lowest_level >= 1 && lowest_level <= PAGE_LEVEL);
+
+	for (r.level = PAGE_LEVEL;; --r.level) {
+		shift = (r.level - 1) * PGDIR_WIDTH + 12;
+		offset = ((unsigned long)virt >> shift) & PGDIR_MASK;
+		r.pte = &pt[offset];
+		pte = *r.pte;
+
+		if (!(pte & PT_PRESENT_MASK))
+			return r;
+
+		if ((r.level == 2 || r.level == 3) && (pte & PT_PAGE_SIZE_MASK))
+			return r;
+
+		if (r.level == lowest_level)
+			return r;
+
+		pt = phys_to_virt(pte & 0xffffffffff000ull);
+	}
+}
+
+/*
+ * Returns the leaf PTE in the mapping of @virt (i.e., 4K PTE or a present huge
+ * PTE). Returns NULL if no leaf PTE exists.
+ */
 unsigned long *get_pte(unsigned long *cr3, void *virt)
 {
-    int level;
-    unsigned long *pt = cr3, pte;
-    unsigned offset;
+	struct pte_search search;
 
-    for (level = PAGE_LEVEL; level > 1; --level) {
-	offset = ((unsigned long)virt >> (((level-1) * PGDIR_WIDTH) + 12)) & PGDIR_MASK;
-	pte = pt[offset];
-	if (!(pte & PT_PRESENT_MASK))
-	    return NULL;
-	if (level == 2 && (pte & PT_PAGE_SIZE_MASK))
-	    return &pt[offset];
-	pt = phys_to_virt(pte & PT_ADDR_MASK);
-    }
-    offset = ((unsigned long)virt >> (((level-1) * PGDIR_WIDTH) + 12)) & PGDIR_MASK;
-    return &pt[offset];
+	search = find_pte_level(cr3, virt, 1);
+	return found_leaf_pte(search) ? search.pte : NULL;
+}
+
+/*
+ * Returns the PTE in the mapping of @virt at the given level @pte_level.
+ * Returns NULL if the PT at @pte_level isn't present (i.e., the mapping at
+ * @pte_level - 1 isn't present).
+ */
+unsigned long *get_pte_level(unsigned long *cr3, void *virt, int pte_level)
+{
+	struct pte_search search;
+
+	search = find_pte_level(cr3, virt, pte_level);
+	return search.level == pte_level ? search.pte : NULL;
 }
 
 unsigned long *install_large_page(unsigned long *cr3,
@@ -173,6 +212,33 @@  unsigned long *install_page(unsigned long *cr3,
     return install_pte(cr3, 1, virt, phys | PT_PRESENT_MASK | PT_WRITABLE_MASK | PT_USER_MASK, 0);
 }
 
+void install_pages(unsigned long *cr3, unsigned long phys, unsigned long len,
+		   void *virt)
+{
+	unsigned long max = (u64)len + (u64)phys;
+	assert(phys % PAGE_SIZE == 0);
+	assert((unsigned long) virt % PAGE_SIZE == 0);
+	assert(len % PAGE_SIZE == 0);
+
+	while (phys + PAGE_SIZE <= max) {
+		install_page(cr3, phys, virt);
+		phys += PAGE_SIZE;
+		virt = (char *) virt + PAGE_SIZE;
+	}
+}
+
+bool any_present_pages(unsigned long *cr3, void *virt, unsigned long len)
+{
+	unsigned long max = (unsigned long) virt + len;
+	unsigned long curr;
+
+	for (curr = (unsigned long) virt; curr < max; curr += PAGE_SIZE) {
+		unsigned long *ptep = get_pte(cr3, (void *) curr);
+		if (ptep && (*ptep & PT_PRESENT_MASK))
+			return true;
+	}
+	return false;
+}
 
 static void setup_mmu_range(unsigned long *cr3, unsigned long start,
 			    unsigned long len)
@@ -184,10 +250,7 @@  static void setup_mmu_range(unsigned long *cr3, unsigned long start,
 		install_large_page(cr3, phys, (void *)(ulong)phys);
 		phys += LARGE_PAGE_SIZE;
 	}
-	while (phys + PAGE_SIZE <= max) {
-		install_page(cr3, phys, (void *)(ulong)phys);
-		phys += PAGE_SIZE;
-	}
+	install_pages(cr3, phys, max - phys, (void *)(ulong)phys);
 }
 
 static void setup_mmu(unsigned long len)
diff --git a/lib/x86/vm.h b/lib/x86/vm.h
index 64e4b566333f..3522ba8b99b0 100644
--- a/lib/x86/vm.h
+++ b/lib/x86/vm.h
@@ -14,7 +14,27 @@  void *alloc_vpage(void);
 void *alloc_vpages(ulong nr);
 uint64_t virt_to_phys_cr3(void *mem);
 
+struct pte_search {
+	int level;
+	unsigned long *pte;
+};
+
+static inline bool found_huge_pte(struct pte_search search)
+{
+	return (search.level == 2 || search.level == 3) &&
+	       (*search.pte & PT_PRESENT_MASK) &&
+	       (*search.pte & PT_PAGE_SIZE_MASK);
+}
+
+static inline bool found_leaf_pte(struct pte_search search)
+{
+	return search.level == 1 || found_huge_pte(search);
+}
+
+struct pte_search find_pte_level(unsigned long *cr3, void *virt,
+				 int lowest_level);
 unsigned long *get_pte(unsigned long *cr3, void *virt);
+unsigned long *get_pte_level(unsigned long *cr3, void *virt, int pte_level);
 unsigned long *install_pte(unsigned long *cr3,
                            int pte_level,
                            void *virt,
@@ -28,5 +48,12 @@  void free_page(void *page);
 unsigned long *install_large_page(unsigned long *cr3,unsigned long phys,
                                   void *virt);
 unsigned long *install_page(unsigned long *cr3, unsigned long phys, void *virt);
+void install_pages(unsigned long *cr3, unsigned long phys, unsigned long len,
+		   void *virt);
+bool any_present_pages(unsigned long *cr3, void *virt, unsigned long len);
 
+static inline void *current_page_table(void)
+{
+	return phys_to_virt(read_cr3());
+}
 #endif