diff --git a/xen/arch/arm/Makefile b/xen/arch/arm/Makefile
index 4d076b2..e358452 100644
--- a/xen/arch/arm/Makefile
+++ b/xen/arch/arm/Makefile
@@ -37,6 +37,7 @@ obj-y += kernel.init.o
 obj-$(CONFIG_LIVEPATCH) += livepatch.o
 obj-y += mem_access.o
 obj-y += mm.o
+obj-y += mm-walk.o
 obj-y += monitor.o
 obj-y += p2m.o
 obj-y += percpu.o
diff --git a/xen/arch/arm/include/asm/mm-walk.h b/xen/arch/arm/include/asm/mm-walk.h
new file mode 100644
index 0000000..770cc89
--- /dev/null
+++ b/xen/arch/arm/include/asm/mm-walk.h
@@ -0,0 +1,53 @@
+#ifndef __ARM_MM_WALK_H__
+#define __ARM_MM_WALK_H__
+
+#include <asm/lpae.h>
+
+#define RECURSIVE_IDX ((unsigned long)(XEN_PT_LPAE_ENTRIES-1))
+#define RECURSIVE_VA (RECURSIVE_IDX << ZEROETH_SHIFT)
+
+/*
+ * Remove all mappings in these tables from Xen's address space
+ * Only makes sense if walking a guest's tables
+ */
+#define WALK_HIDE_GUEST_MAPPING (1U << 0)
+/*
+ * Remove all mappings to these tables from Xen's address space
+ * Makes sense if walking a guest's table (hide guest tables from Xen)
+ * Or if walking Xen's tables (lock Xen's virtual memory configuration)
+ */
+#define WALK_HIDE_GUEST_TABLE (1U << 1)
+
+/*
+ * Before we can hide individual table entires,
+ * we need to split the directmap superpages
+ */
+#define WALK_SPLIT_DIRECTMAP_TABLE (1U << 2)
+/*
+ * Like walk table hide, but using recursive mapping
+ * to bypass walking directmap when table is in the directmap
+ */
+#define WALK_HIDE_DIRECTMAP_TABLE (1U << 3)
+
+/* These are useful for development/debug */
+/* Show all pte's for a given address space */
+#define WALK_DUMP_ENTRIES (1U << 4)
+/* Show all mappings for a given address space */
+#define WALK_DUMP_MAPPINGS (1U << 5)
+
+/*
+ * Given the value of a ttbr register, this function walks every valid entry in the trie
+ * (As opposed to dump_pt_walk, which follows a single address from root to leaf)
+ */
+void do_walk_tables(paddr_t ttbr, int root_level, int nr_root_tables, int flags);
+
+#endif /*  __ARM_MM_WALK_H__ */
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/arch/arm/include/asm/mm.h b/xen/arch/arm/include/asm/mm.h
index 68adcac..2e85885 100644
--- a/xen/arch/arm/include/asm/mm.h
+++ b/xen/arch/arm/include/asm/mm.h
@@ -209,6 +209,17 @@ extern void mmu_init_secondary_cpu(void);
  * For Arm64, map the region in the directmap area.
  */
 extern void setup_directmap_mappings(unsigned long base_mfn, unsigned long nr_mfns);
+/* Shatter superpages for these mfns if needed */
+extern int split_directmap_mapping(unsigned long mfn, unsigned long nr_mfns);
+/* Remove these mfns from the directmap */
+extern int destroy_directmap_mapping(unsigned long mfn, unsigned long nr_mfns);
+/*
+ * Remove this mfn from the directmap (bypassing normal update code)
+ * This is a workaround for current pgtable update code, which cannot be used
+ * to remove directmap table entries from the directmap (because they are
+ * needed to walk the directmap)
+ */
+extern void destroy_directmap_table(unsigned long mfn);
 /* Map a frame table to cover physical addresses ps through pe */
 extern void setup_frametable_mappings(paddr_t ps, paddr_t pe);
 /* map a physical range in virtual memory */
diff --git a/xen/arch/arm/mm-walk.c b/xen/arch/arm/mm-walk.c
new file mode 100644
index 0000000..48f9b2d
--- /dev/null
+++ b/xen/arch/arm/mm-walk.c
@@ -0,0 +1,181 @@
+/*
+ * xen/arch/arm/mm-walk.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <xen/lib.h>
+#include <xen/domain_page.h>
+
+#include <asm/page.h>
+#include <asm/mm-walk.h>
+
+typedef struct {
+    /* Keeps track of all the table offsets so we can reconstruct the VA if we need to */
+    int off[4];
+
+    /* Keeps track of root level so we can make sense of the table offsets */
+    int root_level;
+    int root_table_idx; /* only meaningful when nr_root_tables > 1 */
+} walk_info_t;
+
+/*
+ * Turn a walk_info_t into a virtual address
+ *
+ * XXX: This only applies to the lower VA range
+ * Ie. if you are looking at a table in ttbr1, this is different
+ * XXX: doesn't work for concat tables right now either
+ */
+static unsigned long walk_to_va(int level, walk_info_t *walk)
+{
+/* #define off_valid(x) (((x) <= level) && ((x) >= walk->root_level)) */
+#define off_valid(x) ((x) <= level)
+#define off_val(x) ((u64)(off_valid(x) ? walk->off[x] : 0))
+
+    return (off_val(0) << ZEROETH_SHIFT)  \
+           | (off_val(1) << FIRST_SHIFT)  \
+           | (off_val(2) << SECOND_SHIFT) \
+           | (off_val(3) << THIRD_SHIFT);
+}
+
+/* Prints each entry in the form "\t @XTH TABLE:0.0.0.0 = 0xENTRY" */
+static void dump_entry(int level, lpae_t pte, walk_info_t *walk)
+{
+    int i;
+    static const char *level_strs[4] = { "0TH", "1ST", "2ND", "3RD" };
+    ASSERT(level <= 3);
+
+    for (i = 0; i < level; i++)
+        printk("  ");
+
+    printk("@%s %i:", level_strs[level], walk->root_table_idx);
+
+    for (i = walk->root_level; i < level; i++)
+        printk("%d.", walk->off[i]);
+
+    printk("%d = 0x%lx\n", walk->off[level], pte.bits);
+}
+
+/* Prints each mapping in the form IA:0xIA -> OFN:0xOFN XG,M,K */
+static void dump_mapping(int level, lpae_t pte, walk_info_t *walk)
+{
+    unsigned long va;
+    unsigned long ofn = pte.walk.base;
+    const char *size[4] = {"??", "1G", "2M", "4K"};
+
+    ASSERT(level >= 1);
+    ASSERT(level <= 3);
+
+    va = walk_to_va(level, walk);
+
+    /* ofn stands for output frame number.. I just made it up. */
+    printk("0x%lx -> 0x%lx %s\n", va, ofn, size[level]);
+}
+
+/* Recursive walk function */
+static void walk_table(mfn_t mfn, int level, walk_info_t *walk, int flags)
+{
+    lpae_t *table;
+
+    #define i (walk->off[level])
+
+    BUG_ON(level > 3);
+
+    table = map_domain_page(mfn);
+    for ( i = 0; i < XEN_PT_LPAE_ENTRIES; i++ )
+    {
+        lpae_t pte = table[i];
+        if ( !lpae_is_valid(pte) )
+            continue;
+
+        /* Skip recursive mapping */
+        if ( level == 0 && i == RECURSIVE_IDX )
+            continue;
+
+        if ( flags & WALK_DUMP_ENTRIES )
+            dump_entry(level, pte, walk);
+
+        if ( lpae_is_mapping(pte, level) )
+        {
+            /* Do mapping related things */
+            if ( flags & WALK_DUMP_MAPPINGS )
+                dump_mapping(level, pte, walk);
+            if ( flags & WALK_HIDE_GUEST_MAPPING )
+                /* Destroy all of Xen's mappings to the physical frames covered by this entry */
+                destroy_directmap_mapping(pte.walk.base, 1 << XEN_PT_LEVEL_ORDER(level));
+        }
+        else if ( lpae_is_table(pte, level) )
+        {
+            /* else, pte is a table: recurse! */
+            walk_table(lpae_get_mfn(pte), level + 1, walk, flags);
+
+            /* Note that the entry is a normal entry in xen's page tables */
+            if ( flags & WALK_HIDE_GUEST_TABLE )
+                /*
+                 * This call will look up the table pointed to by this entry in the directmap
+                 * and remove it in the typical way
+                 * This leaves the table intact, but removes the directmap mapping to it, hiding it from xen
+                 */
+                destroy_directmap_mapping(pte.walk.base, 1);
+            if ( flags & WALK_SPLIT_DIRECTMAP_TABLE )
+                /*
+                 * This call will look up the table pointed to by this entry in the directmap
+                 * and make sure that it has it's own l3 entry, splitting superpages if needed
+                 */
+                split_directmap_mapping(pte.walk.base, 1);
+            if ( flags & WALK_HIDE_DIRECTMAP_TABLE )
+                /*
+                 * This call will look up the table pointed to by this entry in the directmap
+                 * and (now that it has it's own l3 entry) overwrite that entry with 0's
+                 * This leaves the table intact, but removes the directmap mapping to it, hiding it from xen
+                 */
+                destroy_directmap_table(pte.walk.base);
+        }
+        /* else, invalid pte, level == 3, vaild == true, table = false */
+    }
+    unmap_domain_page(table);
+
+    #undef i
+}
+
+void do_walk_tables(paddr_t ttbr, int root_level, int nr_root_tables, int flags)
+{
+    int i;
+    mfn_t root = maddr_to_mfn(ttbr & PADDR_MASK);
+    walk_info_t walk = {
+        .off = {0},
+        .root_level = root_level,
+    };
+
+    BUG_ON( !mfn_x(root) || !mfn_valid(root) );
+
+    for ( i = 0; i < nr_root_tables; i++, root = mfn_add(root, 1) ) {
+        walk.root_table_idx = i;
+        walk_table(root, root_level, &walk, flags);
+
+        /* Our walk doesn't consider the root table, so do that here */
+        if ( flags & WALK_SPLIT_DIRECTMAP_TABLE )
+            split_directmap_mapping(mfn_x(root), 1);
+        if ( flags & WALK_HIDE_GUEST_TABLE )
+            destroy_directmap_mapping(mfn_x(root), 1);
+        if ( flags & WALK_HIDE_DIRECTMAP_TABLE )
+            destroy_directmap_table(mfn_x(root));
+    }
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/arch/arm/mm.c b/xen/arch/arm/mm.c
index 91b9c2b..64e9efd 100644
--- a/xen/arch/arm/mm.c
+++ b/xen/arch/arm/mm.c
@@ -21,11 +21,13 @@
 #include <xen/sizes.h>
 #include <xen/types.h>
 #include <xen/vmap.h>
+#include <xen/vmf.h>
 
 #include <xsm/xsm.h>
 
 #include <asm/fixmap.h>
 #include <asm/setup.h>
+#include <asm/mm-walk.h>
 
 #include <public/memory.h>
 
@@ -1164,7 +1166,8 @@ static int xen_pt_update(unsigned long virt,
      *
      * XXX: Add a check.
      */
-    const mfn_t root = virt_to_mfn(THIS_CPU_PGTABLE);
+    /* TODO: does this change have a negative performance impact? */
+    const mfn_t root = maddr_to_mfn(READ_SYSREG64(TTBR0_EL2));
 
     /*
      * The hardware was configured to forbid mapping both writeable and
@@ -1273,6 +1276,199 @@ int modify_xen_mappings(unsigned long s, unsigned long e, unsigned int flags)
     return xen_pt_update(s, INVALID_MFN, (e - s) >> PAGE_SHIFT, flags);
 }
 
+static void insert_recursive_mapping(void)
+{
+    uint64_t ttbr = READ_SYSREG64(TTBR0_EL2);
+    const mfn_t root_mfn = maddr_to_mfn(ttbr & PADDR_MASK);
+    lpae_t *pgtable = map_domain_page(root_mfn);
+
+    lpae_t pte = mfn_to_xen_entry(root_mfn, MT_NORMAL);
+    pte.pt.table = 1;
+
+    spin_lock(&xen_pt_lock);
+
+    write_pte(&pgtable[RECURSIVE_IDX], pte);
+    clean_dcache(pgtable[RECURSIVE_IDX]);
+
+    unmap_domain_page(pgtable);
+    spin_unlock(&xen_pt_lock);
+}
+
+/*
+ * Converts va to a table pointer through the recursive mapping
+ * Only valid for the current address space obviously
+ */
+static lpae_t *va_to_table(int level, unsigned long va)
+{
+    /* Shift everything by 9 for each walk we skip */
+    /* Last off shifted out becomes becomes offset into page */
+    for ( ;level <= 3; level++ ) {
+        va >>= XEN_PT_LPAE_SHIFT;
+        va |= RECURSIVE_VA;
+    }
+
+    /* Mask out any offset, in case caller is asking about a misalligned va */
+    va &= ~0x7;
+    return (lpae_t *)va;
+}
+
+/*
+ * Zero out the table at level when walking to virt
+ * Do this through the recursive mapping, in case we have already
+ * removed part of the directmap and can't walk to that entry
+ */
+static void clear_pte_directly(int level, void *virt)
+{
+    unsigned long va = (unsigned long)virt;
+    lpae_t empty = {.pt = {0x0}};
+    lpae_t *table;
+
+    spin_lock(&xen_pt_lock);
+
+    /* We're assuming we can safely remove an entry at `level` */
+    /* This depends on va not living in a superpage */
+    BUG_ON(level > 1 && !va_to_table(1, va)->pt.table);
+    BUG_ON(level > 2 && !va_to_table(2, va)->pt.table);
+
+    table = va_to_table(level, va);
+    write_pte(table, empty);
+    clean_dcache(*table);
+    flush_xen_tlb_range_va((vaddr_t)table, sizeof(*table));
+
+    spin_unlock(&xen_pt_lock);
+}
+
+static void remove_recursive_mapping(void)
+{
+    clear_pte_directly(0, (void *)RECURSIVE_VA);
+}
+
+static int modify_virt_mapping(void *virt, int nr_pages, int flags)
+{
+    unsigned long va = (unsigned long)virt;
+    return modify_xen_mappings(va, va + (PAGE_SIZE * nr_pages), flags);
+}
+
+static int destroy_virt_mapping(void *virt, int nr_pages)
+{
+    return modify_virt_mapping(virt, nr_pages, 0);
+}
+
+static int modify_directmap_mapping(unsigned long mfn, unsigned long nr_mfns, int flags)
+{
+    if ( mfn & pfn_hole_mask )
+    {
+        printk("** Skipping mfn 0x%lx because it lives in the pfn hole **\n", mfn);
+        return 0;
+    }
+
+    return modify_virt_mapping(__mfn_to_virt(mfn), nr_mfns, flags);
+}
+
+int split_directmap_mapping(unsigned long mfn, unsigned long nr_mfns)
+{
+    return modify_directmap_mapping(mfn, nr_mfns, PAGE_HYPERVISOR);
+}
+
+int destroy_directmap_mapping(unsigned long mfn, unsigned long nr_mfns)
+{
+    return modify_directmap_mapping(mfn, nr_mfns, 0);
+}
+
+void destroy_directmap_table(unsigned long mfn)
+{
+    BUG_ON(mfn & pfn_hole_mask);
+    clear_pte_directly(3, __mfn_to_virt(mfn));
+}
+
+static void unmap_xen_root_tables(void)
+{
+    destroy_virt_mapping(xen_xenmap, 1);
+    destroy_virt_mapping(xen_fixmap, 1);
+    destroy_virt_mapping(xen_second, 1);
+#if defined(CONFIG_ARM_64)
+    destroy_virt_mapping(xen_first, 1);
+    destroy_virt_mapping(xen_pgtable, 1);
+#endif
+}
+
+static void walk_hyp_tables(int flags)
+{
+    uint64_t httbr = READ_SYSREG64(TTBR0_EL2);
+    do_walk_tables(httbr, HYP_PT_ROOT_LEVEL, 1, flags);
+}
+
+static void walk_guest_tables(struct domain *d, int flags)
+{
+    uint64_t vttbr = d->arch.p2m.vttbr;
+    do_walk_tables(vttbr, P2M_ROOT_LEVEL, 1<<P2M_ROOT_ORDER, flags);
+}
+
+
+void vmf_unmap_guest(struct domain *d)
+{
+    /* Remove all of directmap mappings to guest */
+    walk_guest_tables(d, WALK_HIDE_GUEST_MAPPING);
+
+    /* Remove all mappings to guest second stage tables */
+    walk_guest_tables(d, WALK_HIDE_GUEST_TABLE);
+}
+
+void vmf_lock_xen_pgtables(void)
+{
+    /* Remove all of the static allocated root tables */
+    unmap_xen_root_tables();
+
+    /*
+     * Remove all tables from directmap
+     * Becuase we can't use the directmap to walk tables while we are removing
+     * the directmap, add a recursive pointer and use that to erase pte's
+     */
+    insert_recursive_mapping();
+    walk_hyp_tables(WALK_SPLIT_DIRECTMAP_TABLE);
+    walk_hyp_tables(WALK_HIDE_DIRECTMAP_TABLE);
+    remove_recursive_mapping();
+}
+
+void vmf_dump_xen_info()
+{
+    printk("Dump reg info...\n");
+    printk("current httbr0 is 0x%lx\n", READ_SYSREG64(TTBR0_EL2));
+    printk("current vttbr is 0x%lx\n", READ_SYSREG64(VTTBR_EL2));
+    printk("current ttbr0 is 0x%lx\n", READ_SYSREG64(TTBR0_EL1));
+    printk("current ttbr1 is 0x%lx\n", READ_SYSREG64(TTBR1_EL1));
+    printk("\n");
+
+    printk("Dump xen table info...\n");
+#if defined(CONFIG_ARM_64)
+    printk("xen_pgtable: 0x%"PRIvaddr"\n", (vaddr_t)xen_pgtable);
+    printk("xen_first: 0x%"PRIvaddr"\n", (vaddr_t)xen_first);
+#endif
+    printk("xen_second: 0x%"PRIvaddr"\n", (vaddr_t)xen_second);
+    printk("xen_xenmap: 0x%"PRIvaddr"\n", (vaddr_t)xen_xenmap);
+    printk("xen_fixmap: 0x%"PRIvaddr"\n", (vaddr_t)xen_fixmap);
+}
+
+void vmf_dump_domain_info(struct domain *d)
+{
+    uint64_t vttbr = d->arch.p2m.vttbr;
+    uint64_t httbr = READ_SYSREG64(TTBR0_EL2);
+
+    printk("Dump domain info...\n");
+    printk("guest mfn = 0x%lx\n", paddr_to_pfn(vttbr & PADDR_MASK));
+    printk("xen mfn = 0x%lx\n", paddr_to_pfn(httbr & PADDR_MASK));
+}
+
+void vmf_dump_xen_tables()
+{
+    walk_hyp_tables(WALK_DUMP_MAPPINGS | WALK_DUMP_ENTRIES);
+}
+
+void vmf_dump_domain_tables(struct domain *d)
+{
+    walk_guest_tables(d, WALK_DUMP_MAPPINGS | WALK_DUMP_ENTRIES);
+}
+
 /* Release all __init and __initdata ranges to be reused */
 void free_init_memory(void)
 {
diff --git a/xen/common/Kconfig b/xen/common/Kconfig
index 3bf92b8..c087371 100644
--- a/xen/common/Kconfig
+++ b/xen/common/Kconfig
@@ -94,6 +94,8 @@ config STATIC_MEMORY
 
 config VMF
 	bool "Virtual Memory Fuse Support"
+	depends on ARM_64
+	default y
 
 menu "Speculative hardening"
 
