Merge branch kvm-arm64/pkvm-vcpu-state into kvmarm-master/next
[linux-2.6-microblaze.git] / arch / arm64 / kvm / hyp / pgtable.c
index 2bcb2d5..b11cf2c 100644 (file)
 #define KVM_INVALID_PTE_OWNER_MASK     GENMASK(9, 2)
 #define KVM_MAX_OWNER_ID               1
 
+/*
+ * Used to indicate a pte for which a 'break-before-make' sequence is in
+ * progress.
+ */
+#define KVM_INVALID_PTE_LOCKED         BIT(10)
+
 struct kvm_pgtable_walk_data {
-       struct kvm_pgtable              *pgt;
        struct kvm_pgtable_walker       *walker;
 
        u64                             addr;
@@ -62,20 +67,20 @@ static bool kvm_phys_is_valid(u64 phys)
        return phys < BIT(id_aa64mmfr0_parange_to_phys_shift(ID_AA64MMFR0_EL1_PARANGE_MAX));
 }
 
-static bool kvm_block_mapping_supported(u64 addr, u64 end, u64 phys, u32 level)
+static bool kvm_block_mapping_supported(const struct kvm_pgtable_visit_ctx *ctx, u64 phys)
 {
-       u64 granule = kvm_granule_size(level);
+       u64 granule = kvm_granule_size(ctx->level);
 
-       if (!kvm_level_supports_block_mapping(level))
+       if (!kvm_level_supports_block_mapping(ctx->level))
                return false;
 
-       if (granule > (end - addr))
+       if (granule > (ctx->end - ctx->addr))
                return false;
 
        if (kvm_phys_is_valid(phys) && !IS_ALIGNED(phys, granule))
                return false;
 
-       return IS_ALIGNED(addr, granule);
+       return IS_ALIGNED(ctx->addr, granule);
 }
 
 static u32 kvm_pgtable_idx(struct kvm_pgtable_walk_data *data, u32 level)
@@ -86,7 +91,7 @@ static u32 kvm_pgtable_idx(struct kvm_pgtable_walk_data *data, u32 level)
        return (data->addr >> shift) & mask;
 }
 
-static u32 __kvm_pgd_page_idx(struct kvm_pgtable *pgt, u64 addr)
+static u32 kvm_pgd_page_idx(struct kvm_pgtable *pgt, u64 addr)
 {
        u64 shift = kvm_granule_shift(pgt->start_level - 1); /* May underflow */
        u64 mask = BIT(pgt->ia_bits) - 1;
@@ -94,11 +99,6 @@ static u32 __kvm_pgd_page_idx(struct kvm_pgtable *pgt, u64 addr)
        return (addr & mask) >> shift;
 }
 
-static u32 kvm_pgd_page_idx(struct kvm_pgtable_walk_data *data)
-{
-       return __kvm_pgd_page_idx(data->pgt, data->addr);
-}
-
 static u32 kvm_pgd_pages(u32 ia_bits, u32 start_level)
 {
        struct kvm_pgtable pgt = {
@@ -106,7 +106,7 @@ static u32 kvm_pgd_pages(u32 ia_bits, u32 start_level)
                .start_level    = start_level,
        };
 
-       return __kvm_pgd_page_idx(&pgt, -1ULL) + 1;
+       return kvm_pgd_page_idx(&pgt, -1ULL) + 1;
 }
 
 static bool kvm_pte_table(kvm_pte_t pte, u32 level)
@@ -130,16 +130,13 @@ static void kvm_clear_pte(kvm_pte_t *ptep)
        WRITE_ONCE(*ptep, 0);
 }
 
-static void kvm_set_table_pte(kvm_pte_t *ptep, kvm_pte_t *childp,
-                             struct kvm_pgtable_mm_ops *mm_ops)
+static kvm_pte_t kvm_init_table_pte(kvm_pte_t *childp, struct kvm_pgtable_mm_ops *mm_ops)
 {
-       kvm_pte_t old = *ptep, pte = kvm_phys_to_pte(mm_ops->virt_to_phys(childp));
+       kvm_pte_t pte = kvm_phys_to_pte(mm_ops->virt_to_phys(childp));
 
        pte |= FIELD_PREP(KVM_PTE_TYPE, KVM_PTE_TYPE_TABLE);
        pte |= KVM_PTE_VALID;
-
-       WARN_ON(kvm_pte_valid(old));
-       smp_store_release(ptep, pte);
+       return pte;
 }
 
 static kvm_pte_t kvm_init_valid_leaf_pte(u64 pa, kvm_pte_t attr, u32 level)
@@ -160,36 +157,47 @@ static kvm_pte_t kvm_init_invalid_leaf_owner(u8 owner_id)
        return FIELD_PREP(KVM_INVALID_PTE_OWNER_MASK, owner_id);
 }
 
-static int kvm_pgtable_visitor_cb(struct kvm_pgtable_walk_data *data, u64 addr,
-                                 u32 level, kvm_pte_t *ptep,
-                                 enum kvm_pgtable_walk_flags flag)
+static int kvm_pgtable_visitor_cb(struct kvm_pgtable_walk_data *data,
+                                 const struct kvm_pgtable_visit_ctx *ctx,
+                                 enum kvm_pgtable_walk_flags visit)
 {
        struct kvm_pgtable_walker *walker = data->walker;
-       return walker->cb(addr, data->end, level, ptep, flag, walker->arg);
+
+       /* Ensure the appropriate lock is held (e.g. RCU lock for stage-2 MMU) */
+       WARN_ON_ONCE(kvm_pgtable_walk_shared(ctx) && !kvm_pgtable_walk_lock_held());
+       return walker->cb(ctx, visit);
 }
 
 static int __kvm_pgtable_walk(struct kvm_pgtable_walk_data *data,
-                             kvm_pte_t *pgtable, u32 level);
+                             struct kvm_pgtable_mm_ops *mm_ops, kvm_pteref_t pgtable, u32 level);
 
 static inline int __kvm_pgtable_visit(struct kvm_pgtable_walk_data *data,
-                                     kvm_pte_t *ptep, u32 level)
+                                     struct kvm_pgtable_mm_ops *mm_ops,
+                                     kvm_pteref_t pteref, u32 level)
 {
-       int ret = 0;
-       u64 addr = data->addr;
-       kvm_pte_t *childp, pte = *ptep;
-       bool table = kvm_pte_table(pte, level);
        enum kvm_pgtable_walk_flags flags = data->walker->flags;
+       kvm_pte_t *ptep = kvm_dereference_pteref(data->walker, pteref);
+       struct kvm_pgtable_visit_ctx ctx = {
+               .ptep   = ptep,
+               .old    = READ_ONCE(*ptep),
+               .arg    = data->walker->arg,
+               .mm_ops = mm_ops,
+               .addr   = data->addr,
+               .end    = data->end,
+               .level  = level,
+               .flags  = flags,
+       };
+       int ret = 0;
+       kvm_pteref_t childp;
+       bool table = kvm_pte_table(ctx.old, level);
 
-       if (table && (flags & KVM_PGTABLE_WALK_TABLE_PRE)) {
-               ret = kvm_pgtable_visitor_cb(data, addr, level, ptep,
-                                            KVM_PGTABLE_WALK_TABLE_PRE);
-       }
+       if (table && (ctx.flags & KVM_PGTABLE_WALK_TABLE_PRE))
+               ret = kvm_pgtable_visitor_cb(data, &ctx, KVM_PGTABLE_WALK_TABLE_PRE);
 
-       if (!table && (flags & KVM_PGTABLE_WALK_LEAF)) {
-               ret = kvm_pgtable_visitor_cb(data, addr, level, ptep,
-                                            KVM_PGTABLE_WALK_LEAF);
-               pte = *ptep;
-               table = kvm_pte_table(pte, level);
+       if (!table && (ctx.flags & KVM_PGTABLE_WALK_LEAF)) {
+               ret = kvm_pgtable_visitor_cb(data, &ctx, KVM_PGTABLE_WALK_LEAF);
+               ctx.old = READ_ONCE(*ptep);
+               table = kvm_pte_table(ctx.old, level);
        }
 
        if (ret)
@@ -201,22 +209,20 @@ static inline int __kvm_pgtable_visit(struct kvm_pgtable_walk_data *data,
                goto out;
        }
 
-       childp = kvm_pte_follow(pte, data->pgt->mm_ops);
-       ret = __kvm_pgtable_walk(data, childp, level + 1);
+       childp = (kvm_pteref_t)kvm_pte_follow(ctx.old, mm_ops);
+       ret = __kvm_pgtable_walk(data, mm_ops, childp, level + 1);
        if (ret)
                goto out;
 
-       if (flags & KVM_PGTABLE_WALK_TABLE_POST) {
-               ret = kvm_pgtable_visitor_cb(data, addr, level, ptep,
-                                            KVM_PGTABLE_WALK_TABLE_POST);
-       }
+       if (ctx.flags & KVM_PGTABLE_WALK_TABLE_POST)
+               ret = kvm_pgtable_visitor_cb(data, &ctx, KVM_PGTABLE_WALK_TABLE_POST);
 
 out:
        return ret;
 }
 
 static int __kvm_pgtable_walk(struct kvm_pgtable_walk_data *data,
-                             kvm_pte_t *pgtable, u32 level)
+                             struct kvm_pgtable_mm_ops *mm_ops, kvm_pteref_t pgtable, u32 level)
 {
        u32 idx;
        int ret = 0;
@@ -225,12 +231,12 @@ static int __kvm_pgtable_walk(struct kvm_pgtable_walk_data *data,
                return -EINVAL;
 
        for (idx = kvm_pgtable_idx(data, level); idx < PTRS_PER_PTE; ++idx) {
-               kvm_pte_t *ptep = &pgtable[idx];
+               kvm_pteref_t pteref = &pgtable[idx];
 
                if (data->addr >= data->end)
                        break;
 
-               ret = __kvm_pgtable_visit(data, ptep, level);
+               ret = __kvm_pgtable_visit(data, mm_ops, pteref, level);
                if (ret)
                        break;
        }
@@ -238,11 +244,10 @@ static int __kvm_pgtable_walk(struct kvm_pgtable_walk_data *data,
        return ret;
 }
 
-static int _kvm_pgtable_walk(struct kvm_pgtable_walk_data *data)
+static int _kvm_pgtable_walk(struct kvm_pgtable *pgt, struct kvm_pgtable_walk_data *data)
 {
        u32 idx;
        int ret = 0;
-       struct kvm_pgtable *pgt = data->pgt;
        u64 limit = BIT(pgt->ia_bits);
 
        if (data->addr > limit || data->end > limit)
@@ -251,10 +256,10 @@ static int _kvm_pgtable_walk(struct kvm_pgtable_walk_data *data)
        if (!pgt->pgd)
                return -EINVAL;
 
-       for (idx = kvm_pgd_page_idx(data); data->addr < data->end; ++idx) {
-               kvm_pte_t *ptep = &pgt->pgd[idx * PTRS_PER_PTE];
+       for (idx = kvm_pgd_page_idx(pgt, data->addr); data->addr < data->end; ++idx) {
+               kvm_pteref_t pteref = &pgt->pgd[idx * PTRS_PER_PTE];
 
-               ret = __kvm_pgtable_walk(data, ptep, pgt->start_level);
+               ret = __kvm_pgtable_walk(data, pgt->mm_ops, pteref, pgt->start_level);
                if (ret)
                        break;
        }
@@ -266,13 +271,20 @@ int kvm_pgtable_walk(struct kvm_pgtable *pgt, u64 addr, u64 size,
                     struct kvm_pgtable_walker *walker)
 {
        struct kvm_pgtable_walk_data walk_data = {
-               .pgt    = pgt,
                .addr   = ALIGN_DOWN(addr, PAGE_SIZE),
                .end    = PAGE_ALIGN(walk_data.addr + size),
                .walker = walker,
        };
+       int r;
+
+       r = kvm_pgtable_walk_begin(walker);
+       if (r)
+               return r;
 
-       return _kvm_pgtable_walk(&walk_data);
+       r = _kvm_pgtable_walk(pgt, &walk_data);
+       kvm_pgtable_walk_end(walker);
+
+       return r;
 }
 
 struct leaf_walk_data {
@@ -280,13 +292,13 @@ struct leaf_walk_data {
        u32             level;
 };
 
-static int leaf_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
-                      enum kvm_pgtable_walk_flags flag, void * const arg)
+static int leaf_walker(const struct kvm_pgtable_visit_ctx *ctx,
+                      enum kvm_pgtable_walk_flags visit)
 {
-       struct leaf_walk_data *data = arg;
+       struct leaf_walk_data *data = ctx->arg;
 
-       data->pte   = *ptep;
-       data->level = level;
+       data->pte   = ctx->old;
+       data->level = ctx->level;
 
        return 0;
 }
@@ -317,7 +329,6 @@ int kvm_pgtable_get_leaf(struct kvm_pgtable *pgt, u64 addr,
 struct hyp_map_data {
        u64                             phys;
        kvm_pte_t                       attr;
-       struct kvm_pgtable_mm_ops       *mm_ops;
 };
 
 static int hyp_set_prot_attr(enum kvm_pgtable_prot prot, kvm_pte_t *ptep)
@@ -371,47 +382,49 @@ enum kvm_pgtable_prot kvm_pgtable_hyp_pte_prot(kvm_pte_t pte)
        return prot;
 }
 
-static bool hyp_map_walker_try_leaf(u64 addr, u64 end, u32 level,
-                                   kvm_pte_t *ptep, struct hyp_map_data *data)
+static bool hyp_map_walker_try_leaf(const struct kvm_pgtable_visit_ctx *ctx,
+                                   struct hyp_map_data *data)
 {
-       kvm_pte_t new, old = *ptep;
-       u64 granule = kvm_granule_size(level), phys = data->phys;
+       kvm_pte_t new;
+       u64 granule = kvm_granule_size(ctx->level), phys = data->phys;
 
-       if (!kvm_block_mapping_supported(addr, end, phys, level))
+       if (!kvm_block_mapping_supported(ctx, phys))
                return false;
 
        data->phys += granule;
-       new = kvm_init_valid_leaf_pte(phys, data->attr, level);
-       if (old == new)
+       new = kvm_init_valid_leaf_pte(phys, data->attr, ctx->level);
+       if (ctx->old == new)
                return true;
-       if (!kvm_pte_valid(old))
-               data->mm_ops->get_page(ptep);
-       else if (WARN_ON((old ^ new) & ~KVM_PTE_LEAF_ATTR_HI_SW))
+       if (!kvm_pte_valid(ctx->old))
+               ctx->mm_ops->get_page(ctx->ptep);
+       else if (WARN_ON((ctx->old ^ new) & ~KVM_PTE_LEAF_ATTR_HI_SW))
                return false;
 
-       smp_store_release(ptep, new);
+       smp_store_release(ctx->ptep, new);
        return true;
 }
 
-static int hyp_map_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
-                         enum kvm_pgtable_walk_flags flag, void * const arg)
+static int hyp_map_walker(const struct kvm_pgtable_visit_ctx *ctx,
+                         enum kvm_pgtable_walk_flags visit)
 {
-       kvm_pte_t *childp;
-       struct hyp_map_data *data = arg;
-       struct kvm_pgtable_mm_ops *mm_ops = data->mm_ops;
+       kvm_pte_t *childp, new;
+       struct hyp_map_data *data = ctx->arg;
+       struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
 
-       if (hyp_map_walker_try_leaf(addr, end, level, ptep, arg))
+       if (hyp_map_walker_try_leaf(ctx, data))
                return 0;
 
-       if (WARN_ON(level == KVM_PGTABLE_MAX_LEVELS - 1))
+       if (WARN_ON(ctx->level == KVM_PGTABLE_MAX_LEVELS - 1))
                return -EINVAL;
 
        childp = (kvm_pte_t *)mm_ops->zalloc_page(NULL);
        if (!childp)
                return -ENOMEM;
 
-       kvm_set_table_pte(ptep, childp, mm_ops);
-       mm_ops->get_page(ptep);
+       new = kvm_init_table_pte(childp, mm_ops);
+       mm_ops->get_page(ctx->ptep);
+       smp_store_release(ctx->ptep, new);
+
        return 0;
 }
 
@@ -421,7 +434,6 @@ int kvm_pgtable_hyp_map(struct kvm_pgtable *pgt, u64 addr, u64 size, u64 phys,
        int ret;
        struct hyp_map_data map_data = {
                .phys   = ALIGN_DOWN(phys, PAGE_SIZE),
-               .mm_ops = pgt->mm_ops,
        };
        struct kvm_pgtable_walker walker = {
                .cb     = hyp_map_walker,
@@ -439,44 +451,39 @@ int kvm_pgtable_hyp_map(struct kvm_pgtable *pgt, u64 addr, u64 size, u64 phys,
        return ret;
 }
 
-struct hyp_unmap_data {
-       u64                             unmapped;
-       struct kvm_pgtable_mm_ops       *mm_ops;
-};
-
-static int hyp_unmap_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
-                           enum kvm_pgtable_walk_flags flag, void * const arg)
+static int hyp_unmap_walker(const struct kvm_pgtable_visit_ctx *ctx,
+                           enum kvm_pgtable_walk_flags visit)
 {
-       kvm_pte_t pte = *ptep, *childp = NULL;
-       u64 granule = kvm_granule_size(level);
-       struct hyp_unmap_data *data = arg;
-       struct kvm_pgtable_mm_ops *mm_ops = data->mm_ops;
+       kvm_pte_t *childp = NULL;
+       u64 granule = kvm_granule_size(ctx->level);
+       u64 *unmapped = ctx->arg;
+       struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
 
-       if (!kvm_pte_valid(pte))
+       if (!kvm_pte_valid(ctx->old))
                return -EINVAL;
 
-       if (kvm_pte_table(pte, level)) {
-               childp = kvm_pte_follow(pte, mm_ops);
+       if (kvm_pte_table(ctx->old, ctx->level)) {
+               childp = kvm_pte_follow(ctx->old, mm_ops);
 
                if (mm_ops->page_count(childp) != 1)
                        return 0;
 
-               kvm_clear_pte(ptep);
+               kvm_clear_pte(ctx->ptep);
                dsb(ishst);
-               __tlbi_level(vae2is, __TLBI_VADDR(addr, 0), level);
+               __tlbi_level(vae2is, __TLBI_VADDR(ctx->addr, 0), ctx->level);
        } else {
-               if (end - addr < granule)
+               if (ctx->end - ctx->addr < granule)
                        return -EINVAL;
 
-               kvm_clear_pte(ptep);
+               kvm_clear_pte(ctx->ptep);
                dsb(ishst);
-               __tlbi_level(vale2is, __TLBI_VADDR(addr, 0), level);
-               data->unmapped += granule;
+               __tlbi_level(vale2is, __TLBI_VADDR(ctx->addr, 0), ctx->level);
+               *unmapped += granule;
        }
 
        dsb(ish);
        isb();
-       mm_ops->put_page(ptep);
+       mm_ops->put_page(ctx->ptep);
 
        if (childp)
                mm_ops->put_page(childp);
@@ -486,12 +493,10 @@ static int hyp_unmap_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
 
 u64 kvm_pgtable_hyp_unmap(struct kvm_pgtable *pgt, u64 addr, u64 size)
 {
-       struct hyp_unmap_data unmap_data = {
-               .mm_ops = pgt->mm_ops,
-       };
+       u64 unmapped = 0;
        struct kvm_pgtable_walker walker = {
                .cb     = hyp_unmap_walker,
-               .arg    = &unmap_data,
+               .arg    = &unmapped,
                .flags  = KVM_PGTABLE_WALK_LEAF | KVM_PGTABLE_WALK_TABLE_POST,
        };
 
@@ -499,7 +504,7 @@ u64 kvm_pgtable_hyp_unmap(struct kvm_pgtable *pgt, u64 addr, u64 size)
                return 0;
 
        kvm_pgtable_walk(pgt, addr, size, &walker);
-       return unmap_data.unmapped;
+       return unmapped;
 }
 
 int kvm_pgtable_hyp_init(struct kvm_pgtable *pgt, u32 va_bits,
@@ -507,7 +512,7 @@ int kvm_pgtable_hyp_init(struct kvm_pgtable *pgt, u32 va_bits,
 {
        u64 levels = ARM64_HW_PGTABLE_LEVELS(va_bits);
 
-       pgt->pgd = (kvm_pte_t *)mm_ops->zalloc_page(NULL);
+       pgt->pgd = (kvm_pteref_t)mm_ops->zalloc_page(NULL);
        if (!pgt->pgd)
                return -ENOMEM;
 
@@ -520,19 +525,18 @@ int kvm_pgtable_hyp_init(struct kvm_pgtable *pgt, u32 va_bits,
        return 0;
 }
 
-static int hyp_free_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
-                          enum kvm_pgtable_walk_flags flag, void * const arg)
+static int hyp_free_walker(const struct kvm_pgtable_visit_ctx *ctx,
+                          enum kvm_pgtable_walk_flags visit)
 {
-       struct kvm_pgtable_mm_ops *mm_ops = arg;
-       kvm_pte_t pte = *ptep;
+       struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
 
-       if (!kvm_pte_valid(pte))
+       if (!kvm_pte_valid(ctx->old))
                return 0;
 
-       mm_ops->put_page(ptep);
+       mm_ops->put_page(ctx->ptep);
 
-       if (kvm_pte_table(pte, level))
-               mm_ops->put_page(kvm_pte_follow(pte, mm_ops));
+       if (kvm_pte_table(ctx->old, ctx->level))
+               mm_ops->put_page(kvm_pte_follow(ctx->old, mm_ops));
 
        return 0;
 }
@@ -542,11 +546,10 @@ void kvm_pgtable_hyp_destroy(struct kvm_pgtable *pgt)
        struct kvm_pgtable_walker walker = {
                .cb     = hyp_free_walker,
                .flags  = KVM_PGTABLE_WALK_LEAF | KVM_PGTABLE_WALK_TABLE_POST,
-               .arg    = pgt->mm_ops,
        };
 
        WARN_ON(kvm_pgtable_walk(pgt, 0, BIT(pgt->ia_bits), &walker));
-       pgt->mm_ops->put_page(pgt->pgd);
+       pgt->mm_ops->put_page(kvm_dereference_pteref(&walker, pgt->pgd));
        pgt->pgd = NULL;
 }
 
@@ -561,8 +564,6 @@ struct stage2_map_data {
        struct kvm_s2_mmu               *mmu;
        void                            *memcache;
 
-       struct kvm_pgtable_mm_ops       *mm_ops;
-
        /* Force mappings to page granularity */
        bool                            force_pte;
 };
@@ -670,19 +671,92 @@ static bool stage2_pte_is_counted(kvm_pte_t pte)
        return !!pte;
 }
 
-static void stage2_put_pte(kvm_pte_t *ptep, struct kvm_s2_mmu *mmu, u64 addr,
-                          u32 level, struct kvm_pgtable_mm_ops *mm_ops)
+static bool stage2_pte_is_locked(kvm_pte_t pte)
+{
+       return !kvm_pte_valid(pte) && (pte & KVM_INVALID_PTE_LOCKED);
+}
+
+static bool stage2_try_set_pte(const struct kvm_pgtable_visit_ctx *ctx, kvm_pte_t new)
+{
+       if (!kvm_pgtable_walk_shared(ctx)) {
+               WRITE_ONCE(*ctx->ptep, new);
+               return true;
+       }
+
+       return cmpxchg(ctx->ptep, ctx->old, new) == ctx->old;
+}
+
+/**
+ * stage2_try_break_pte() - Invalidates a pte according to the
+ *                         'break-before-make' requirements of the
+ *                         architecture.
+ *
+ * @ctx: context of the visited pte.
+ * @mmu: stage-2 mmu
+ *
+ * Returns: true if the pte was successfully broken.
+ *
+ * If the removed pte was valid, performs the necessary serialization and TLB
+ * invalidation for the old value. For counted ptes, drops the reference count
+ * on the containing table page.
+ */
+static bool stage2_try_break_pte(const struct kvm_pgtable_visit_ctx *ctx,
+                                struct kvm_s2_mmu *mmu)
+{
+       struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
+
+       if (stage2_pte_is_locked(ctx->old)) {
+               /*
+                * Should never occur if this walker has exclusive access to the
+                * page tables.
+                */
+               WARN_ON(!kvm_pgtable_walk_shared(ctx));
+               return false;
+       }
+
+       if (!stage2_try_set_pte(ctx, KVM_INVALID_PTE_LOCKED))
+               return false;
+
+       /*
+        * Perform the appropriate TLB invalidation based on the evicted pte
+        * value (if any).
+        */
+       if (kvm_pte_table(ctx->old, ctx->level))
+               kvm_call_hyp(__kvm_tlb_flush_vmid, mmu);
+       else if (kvm_pte_valid(ctx->old))
+               kvm_call_hyp(__kvm_tlb_flush_vmid_ipa, mmu, ctx->addr, ctx->level);
+
+       if (stage2_pte_is_counted(ctx->old))
+               mm_ops->put_page(ctx->ptep);
+
+       return true;
+}
+
+static void stage2_make_pte(const struct kvm_pgtable_visit_ctx *ctx, kvm_pte_t new)
+{
+       struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
+
+       WARN_ON(!stage2_pte_is_locked(*ctx->ptep));
+
+       if (stage2_pte_is_counted(new))
+               mm_ops->get_page(ctx->ptep);
+
+       smp_store_release(ctx->ptep, new);
+}
+
+static void stage2_put_pte(const struct kvm_pgtable_visit_ctx *ctx, struct kvm_s2_mmu *mmu,
+                          struct kvm_pgtable_mm_ops *mm_ops)
 {
        /*
         * Clear the existing PTE, and perform break-before-make with
         * TLB maintenance if it was valid.
         */
-       if (kvm_pte_valid(*ptep)) {
-               kvm_clear_pte(ptep);
-               kvm_call_hyp(__kvm_tlb_flush_vmid_ipa, mmu, addr, level);
+       if (kvm_pte_valid(ctx->old)) {
+               kvm_clear_pte(ctx->ptep);
+               kvm_call_hyp(__kvm_tlb_flush_vmid_ipa, mmu, ctx->addr, ctx->level);
        }
 
-       mm_ops->put_page(ptep);
+       mm_ops->put_page(ctx->ptep);
 }
 
 static bool stage2_pte_cacheable(struct kvm_pgtable *pgt, kvm_pte_t pte)
@@ -696,44 +770,42 @@ static bool stage2_pte_executable(kvm_pte_t pte)
        return !(pte & KVM_PTE_LEAF_ATTR_HI_S2_XN);
 }
 
-static bool stage2_leaf_mapping_allowed(u64 addr, u64 end, u32 level,
+static bool stage2_leaf_mapping_allowed(const struct kvm_pgtable_visit_ctx *ctx,
                                        struct stage2_map_data *data)
 {
-       if (data->force_pte && (level < (KVM_PGTABLE_MAX_LEVELS - 1)))
+       if (data->force_pte && (ctx->level < (KVM_PGTABLE_MAX_LEVELS - 1)))
                return false;
 
-       return kvm_block_mapping_supported(addr, end, data->phys, level);
+       return kvm_block_mapping_supported(ctx, data->phys);
 }
 
-static int stage2_map_walker_try_leaf(u64 addr, u64 end, u32 level,
-                                     kvm_pte_t *ptep,
+static int stage2_map_walker_try_leaf(const struct kvm_pgtable_visit_ctx *ctx,
                                      struct stage2_map_data *data)
 {
-       kvm_pte_t new, old = *ptep;
-       u64 granule = kvm_granule_size(level), phys = data->phys;
+       kvm_pte_t new;
+       u64 granule = kvm_granule_size(ctx->level), phys = data->phys;
        struct kvm_pgtable *pgt = data->mmu->pgt;
-       struct kvm_pgtable_mm_ops *mm_ops = data->mm_ops;
+       struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
 
-       if (!stage2_leaf_mapping_allowed(addr, end, level, data))
+       if (!stage2_leaf_mapping_allowed(ctx, data))
                return -E2BIG;
 
        if (kvm_phys_is_valid(phys))
-               new = kvm_init_valid_leaf_pte(phys, data->attr, level);
+               new = kvm_init_valid_leaf_pte(phys, data->attr, ctx->level);
        else
                new = kvm_init_invalid_leaf_owner(data->owner_id);
 
-       if (stage2_pte_is_counted(old)) {
-               /*
-                * Skip updating the PTE if we are trying to recreate the exact
-                * same mapping or only change the access permissions. Instead,
-                * the vCPU will exit one more time from guest if still needed
-                * and then go through the path of relaxing permissions.
-                */
-               if (!stage2_pte_needs_update(old, new))
-                       return -EAGAIN;
+       /*
+        * Skip updating the PTE if we are trying to recreate the exact
+        * same mapping or only change the access permissions. Instead,
+        * the vCPU will exit one more time from guest if still needed
+        * and then go through the path of relaxing permissions.
+        */
+       if (!stage2_pte_needs_update(ctx->old, new))
+               return -EAGAIN;
 
-               stage2_put_pte(ptep, data->mmu, addr, level, mm_ops);
-       }
+       if (!stage2_try_break_pte(ctx, data->mmu))
+               return -EAGAIN;
 
        /* Perform CMOs before installation of the guest stage-2 PTE */
        if (mm_ops->dcache_clean_inval_poc && stage2_pte_cacheable(pgt, new))
@@ -743,56 +815,43 @@ static int stage2_map_walker_try_leaf(u64 addr, u64 end, u32 level,
        if (mm_ops->icache_inval_pou && stage2_pte_executable(new))
                mm_ops->icache_inval_pou(kvm_pte_follow(new, mm_ops), granule);
 
-       smp_store_release(ptep, new);
-       if (stage2_pte_is_counted(new))
-               mm_ops->get_page(ptep);
+       stage2_make_pte(ctx, new);
+
        if (kvm_phys_is_valid(phys))
                data->phys += granule;
        return 0;
 }
 
-static int stage2_map_walk_table_pre(u64 addr, u64 end, u32 level,
-                                    kvm_pte_t *ptep,
+static int stage2_map_walk_table_pre(const struct kvm_pgtable_visit_ctx *ctx,
                                     struct stage2_map_data *data)
 {
-       if (data->anchor)
-               return 0;
+       struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
+       kvm_pte_t *childp = kvm_pte_follow(ctx->old, mm_ops);
+       int ret;
 
-       if (!stage2_leaf_mapping_allowed(addr, end, level, data))
+       if (!stage2_leaf_mapping_allowed(ctx, data))
                return 0;
 
-       data->childp = kvm_pte_follow(*ptep, data->mm_ops);
-       kvm_clear_pte(ptep);
+       ret = stage2_map_walker_try_leaf(ctx, data);
+       if (ret)
+               return ret;
 
-       /*
-        * Invalidate the whole stage-2, as we may have numerous leaf
-        * entries below us which would otherwise need invalidating
-        * individually.
-        */
-       kvm_call_hyp(__kvm_tlb_flush_vmid, data->mmu);
-       data->anchor = ptep;
+       mm_ops->free_removed_table(childp, ctx->level);
        return 0;
 }
 
-static int stage2_map_walk_leaf(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
+static int stage2_map_walk_leaf(const struct kvm_pgtable_visit_ctx *ctx,
                                struct stage2_map_data *data)
 {
-       struct kvm_pgtable_mm_ops *mm_ops = data->mm_ops;
-       kvm_pte_t *childp, pte = *ptep;
+       struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
+       kvm_pte_t *childp, new;
        int ret;
 
-       if (data->anchor) {
-               if (stage2_pte_is_counted(pte))
-                       mm_ops->put_page(ptep);
-
-               return 0;
-       }
-
-       ret = stage2_map_walker_try_leaf(addr, end, level, ptep, data);
+       ret = stage2_map_walker_try_leaf(ctx, data);
        if (ret != -E2BIG)
                return ret;
 
-       if (WARN_ON(level == KVM_PGTABLE_MAX_LEVELS - 1))
+       if (WARN_ON(ctx->level == KVM_PGTABLE_MAX_LEVELS - 1))
                return -EINVAL;
 
        if (!data->memcache)
@@ -802,99 +861,62 @@ static int stage2_map_walk_leaf(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
        if (!childp)
                return -ENOMEM;
 
+       if (!stage2_try_break_pte(ctx, data->mmu)) {
+               mm_ops->put_page(childp);
+               return -EAGAIN;
+       }
+
        /*
         * If we've run into an existing block mapping then replace it with
         * a table. Accesses beyond 'end' that fall within the new table
         * will be mapped lazily.
         */
-       if (stage2_pte_is_counted(pte))
-               stage2_put_pte(ptep, data->mmu, addr, level, mm_ops);
-
-       kvm_set_table_pte(ptep, childp, mm_ops);
-       mm_ops->get_page(ptep);
+       new = kvm_init_table_pte(childp, mm_ops);
+       stage2_make_pte(ctx, new);
 
        return 0;
 }
 
-static int stage2_map_walk_table_post(u64 addr, u64 end, u32 level,
-                                     kvm_pte_t *ptep,
-                                     struct stage2_map_data *data)
-{
-       struct kvm_pgtable_mm_ops *mm_ops = data->mm_ops;
-       kvm_pte_t *childp;
-       int ret = 0;
-
-       if (!data->anchor)
-               return 0;
-
-       if (data->anchor == ptep) {
-               childp = data->childp;
-               data->anchor = NULL;
-               data->childp = NULL;
-               ret = stage2_map_walk_leaf(addr, end, level, ptep, data);
-       } else {
-               childp = kvm_pte_follow(*ptep, mm_ops);
-       }
-
-       mm_ops->put_page(childp);
-       mm_ops->put_page(ptep);
-
-       return ret;
-}
-
 /*
- * This is a little fiddly, as we use all three of the walk flags. The idea
- * is that the TABLE_PRE callback runs for table entries on the way down,
- * looking for table entries which we could conceivably replace with a
- * block entry for this mapping. If it finds one, then it sets the 'anchor'
- * field in 'struct stage2_map_data' to point at the table entry, before
- * clearing the entry to zero and descending into the now detached table.
+ * The TABLE_PRE callback runs for table entries on the way down, looking
+ * for table entries which we could conceivably replace with a block entry
+ * for this mapping. If it finds one it replaces the entry and calls
+ * kvm_pgtable_mm_ops::free_removed_table() to tear down the detached table.
  *
- * The behaviour of the LEAF callback then depends on whether or not the
- * anchor has been set. If not, then we're not using a block mapping higher
- * up the table and we perform the mapping at the existing leaves instead.
- * If, on the other hand, the anchor _is_ set, then we drop references to
- * all valid leaves so that the pages beneath the anchor can be freed.
- *
- * Finally, the TABLE_POST callback does nothing if the anchor has not
- * been set, but otherwise frees the page-table pages while walking back up
- * the page-table, installing the block entry when it revisits the anchor
- * pointer and clearing the anchor to NULL.
+ * Otherwise, the LEAF callback performs the mapping at the existing leaves
+ * instead.
  */
-static int stage2_map_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
-                            enum kvm_pgtable_walk_flags flag, void * const arg)
+static int stage2_map_walker(const struct kvm_pgtable_visit_ctx *ctx,
+                            enum kvm_pgtable_walk_flags visit)
 {
-       struct stage2_map_data *data = arg;
+       struct stage2_map_data *data = ctx->arg;
 
-       switch (flag) {
+       switch (visit) {
        case KVM_PGTABLE_WALK_TABLE_PRE:
-               return stage2_map_walk_table_pre(addr, end, level, ptep, data);
+               return stage2_map_walk_table_pre(ctx, data);
        case KVM_PGTABLE_WALK_LEAF:
-               return stage2_map_walk_leaf(addr, end, level, ptep, data);
-       case KVM_PGTABLE_WALK_TABLE_POST:
-               return stage2_map_walk_table_post(addr, end, level, ptep, data);
+               return stage2_map_walk_leaf(ctx, data);
+       default:
+               return -EINVAL;
        }
-
-       return -EINVAL;
 }
 
 int kvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u64 addr, u64 size,
                           u64 phys, enum kvm_pgtable_prot prot,
-                          void *mc)
+                          void *mc, enum kvm_pgtable_walk_flags flags)
 {
        int ret;
        struct stage2_map_data map_data = {
                .phys           = ALIGN_DOWN(phys, PAGE_SIZE),
                .mmu            = pgt->mmu,
                .memcache       = mc,
-               .mm_ops         = pgt->mm_ops,
                .force_pte      = pgt->force_pte_cb && pgt->force_pte_cb(addr, addr + size, prot),
        };
        struct kvm_pgtable_walker walker = {
                .cb             = stage2_map_walker,
-               .flags          = KVM_PGTABLE_WALK_TABLE_PRE |
-                                 KVM_PGTABLE_WALK_LEAF |
-                                 KVM_PGTABLE_WALK_TABLE_POST,
+               .flags          = flags |
+                                 KVM_PGTABLE_WALK_TABLE_PRE |
+                                 KVM_PGTABLE_WALK_LEAF,
                .arg            = &map_data,
        };
 
@@ -918,15 +940,13 @@ int kvm_pgtable_stage2_set_owner(struct kvm_pgtable *pgt, u64 addr, u64 size,
                .phys           = KVM_PHYS_INVALID,
                .mmu            = pgt->mmu,
                .memcache       = mc,
-               .mm_ops         = pgt->mm_ops,
                .owner_id       = owner_id,
                .force_pte      = true,
        };
        struct kvm_pgtable_walker walker = {
                .cb             = stage2_map_walker,
                .flags          = KVM_PGTABLE_WALK_TABLE_PRE |
-                                 KVM_PGTABLE_WALK_LEAF |
-                                 KVM_PGTABLE_WALK_TABLE_POST,
+                                 KVM_PGTABLE_WALK_LEAF,
                .arg            = &map_data,
        };
 
@@ -937,30 +957,29 @@ int kvm_pgtable_stage2_set_owner(struct kvm_pgtable *pgt, u64 addr, u64 size,
        return ret;
 }
 
-static int stage2_unmap_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
-                              enum kvm_pgtable_walk_flags flag,
-                              void * const arg)
+static int stage2_unmap_walker(const struct kvm_pgtable_visit_ctx *ctx,
+                              enum kvm_pgtable_walk_flags visit)
 {
-       struct kvm_pgtable *pgt = arg;
+       struct kvm_pgtable *pgt = ctx->arg;
        struct kvm_s2_mmu *mmu = pgt->mmu;
-       struct kvm_pgtable_mm_ops *mm_ops = pgt->mm_ops;
-       kvm_pte_t pte = *ptep, *childp = NULL;
+       struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
+       kvm_pte_t *childp = NULL;
        bool need_flush = false;
 
-       if (!kvm_pte_valid(pte)) {
-               if (stage2_pte_is_counted(pte)) {
-                       kvm_clear_pte(ptep);
-                       mm_ops->put_page(ptep);
+       if (!kvm_pte_valid(ctx->old)) {
+               if (stage2_pte_is_counted(ctx->old)) {
+                       kvm_clear_pte(ctx->ptep);
+                       mm_ops->put_page(ctx->ptep);
                }
                return 0;
        }
 
-       if (kvm_pte_table(pte, level)) {
-               childp = kvm_pte_follow(pte, mm_ops);
+       if (kvm_pte_table(ctx->old, ctx->level)) {
+               childp = kvm_pte_follow(ctx->old, mm_ops);
 
                if (mm_ops->page_count(childp) != 1)
                        return 0;
-       } else if (stage2_pte_cacheable(pgt, pte)) {
+       } else if (stage2_pte_cacheable(pgt, ctx->old)) {
                need_flush = !stage2_has_fwb(pgt);
        }
 
@@ -969,11 +988,11 @@ static int stage2_unmap_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
         * block entry and rely on the remaining portions being faulted
         * back lazily.
         */
-       stage2_put_pte(ptep, mmu, addr, level, mm_ops);
+       stage2_put_pte(ctx, mmu, mm_ops);
 
        if (need_flush && mm_ops->dcache_clean_inval_poc)
-               mm_ops->dcache_clean_inval_poc(kvm_pte_follow(pte, mm_ops),
-                                              kvm_granule_size(level));
+               mm_ops->dcache_clean_inval_poc(kvm_pte_follow(ctx->old, mm_ops),
+                                              kvm_granule_size(ctx->level));
 
        if (childp)
                mm_ops->put_page(childp);
@@ -997,21 +1016,19 @@ struct stage2_attr_data {
        kvm_pte_t                       attr_clr;
        kvm_pte_t                       pte;
        u32                             level;
-       struct kvm_pgtable_mm_ops       *mm_ops;
 };
 
-static int stage2_attr_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
-                             enum kvm_pgtable_walk_flags flag,
-                             void * const arg)
+static int stage2_attr_walker(const struct kvm_pgtable_visit_ctx *ctx,
+                             enum kvm_pgtable_walk_flags visit)
 {
-       kvm_pte_t pte = *ptep;
-       struct stage2_attr_data *data = arg;
-       struct kvm_pgtable_mm_ops *mm_ops = data->mm_ops;
+       kvm_pte_t pte = ctx->old;
+       struct stage2_attr_data *data = ctx->arg;
+       struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
 
-       if (!kvm_pte_valid(pte))
+       if (!kvm_pte_valid(ctx->old))
                return 0;
 
-       data->level = level;
+       data->level = ctx->level;
        data->pte = pte;
        pte &= ~data->attr_clr;
        pte |= data->attr_set;
@@ -1027,10 +1044,12 @@ static int stage2_attr_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
                 * stage-2 PTE if we are going to add executable permission.
                 */
                if (mm_ops->icache_inval_pou &&
-                   stage2_pte_executable(pte) && !stage2_pte_executable(*ptep))
+                   stage2_pte_executable(pte) && !stage2_pte_executable(ctx->old))
                        mm_ops->icache_inval_pou(kvm_pte_follow(pte, mm_ops),
-                                                 kvm_granule_size(level));
-               WRITE_ONCE(*ptep, pte);
+                                                 kvm_granule_size(ctx->level));
+
+               if (!stage2_try_set_pte(ctx, pte))
+                       return -EAGAIN;
        }
 
        return 0;
@@ -1039,19 +1058,18 @@ static int stage2_attr_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
 static int stage2_update_leaf_attrs(struct kvm_pgtable *pgt, u64 addr,
                                    u64 size, kvm_pte_t attr_set,
                                    kvm_pte_t attr_clr, kvm_pte_t *orig_pte,
-                                   u32 *level)
+                                   u32 *level, enum kvm_pgtable_walk_flags flags)
 {
        int ret;
        kvm_pte_t attr_mask = KVM_PTE_LEAF_ATTR_LO | KVM_PTE_LEAF_ATTR_HI;
        struct stage2_attr_data data = {
                .attr_set       = attr_set & attr_mask,
                .attr_clr       = attr_clr & attr_mask,
-               .mm_ops         = pgt->mm_ops,
        };
        struct kvm_pgtable_walker walker = {
                .cb             = stage2_attr_walker,
                .arg            = &data,
-               .flags          = KVM_PGTABLE_WALK_LEAF,
+               .flags          = flags | KVM_PGTABLE_WALK_LEAF,
        };
 
        ret = kvm_pgtable_walk(pgt, addr, size, &walker);
@@ -1070,14 +1088,14 @@ int kvm_pgtable_stage2_wrprotect(struct kvm_pgtable *pgt, u64 addr, u64 size)
 {
        return stage2_update_leaf_attrs(pgt, addr, size, 0,
                                        KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W,
-                                       NULL, NULL);
+                                       NULL, NULL, 0);
 }
 
 kvm_pte_t kvm_pgtable_stage2_mkyoung(struct kvm_pgtable *pgt, u64 addr)
 {
        kvm_pte_t pte = 0;
        stage2_update_leaf_attrs(pgt, addr, 1, KVM_PTE_LEAF_ATTR_LO_S2_AF, 0,
-                                &pte, NULL);
+                                &pte, NULL, 0);
        dsb(ishst);
        return pte;
 }
@@ -1086,7 +1104,7 @@ kvm_pte_t kvm_pgtable_stage2_mkold(struct kvm_pgtable *pgt, u64 addr)
 {
        kvm_pte_t pte = 0;
        stage2_update_leaf_attrs(pgt, addr, 1, 0, KVM_PTE_LEAF_ATTR_LO_S2_AF,
-                                &pte, NULL);
+                                &pte, NULL, 0);
        /*
         * "But where's the TLBI?!", you scream.
         * "Over in the core code", I sigh.
@@ -1099,7 +1117,7 @@ kvm_pte_t kvm_pgtable_stage2_mkold(struct kvm_pgtable *pgt, u64 addr)
 bool kvm_pgtable_stage2_is_young(struct kvm_pgtable *pgt, u64 addr)
 {
        kvm_pte_t pte = 0;
-       stage2_update_leaf_attrs(pgt, addr, 1, 0, 0, &pte, NULL);
+       stage2_update_leaf_attrs(pgt, addr, 1, 0, 0, &pte, NULL, 0);
        return pte & KVM_PTE_LEAF_ATTR_LO_S2_AF;
 }
 
@@ -1122,26 +1140,25 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr,
        if (prot & KVM_PGTABLE_PROT_X)
                clr |= KVM_PTE_LEAF_ATTR_HI_S2_XN;
 
-       ret = stage2_update_leaf_attrs(pgt, addr, 1, set, clr, NULL, &level);
+       ret = stage2_update_leaf_attrs(pgt, addr, 1, set, clr, NULL, &level,
+                                      KVM_PGTABLE_WALK_SHARED);
        if (!ret)
                kvm_call_hyp(__kvm_tlb_flush_vmid_ipa, pgt->mmu, addr, level);
        return ret;
 }
 
-static int stage2_flush_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
-                              enum kvm_pgtable_walk_flags flag,
-                              void * const arg)
+static int stage2_flush_walker(const struct kvm_pgtable_visit_ctx *ctx,
+                              enum kvm_pgtable_walk_flags visit)
 {
-       struct kvm_pgtable *pgt = arg;
+       struct kvm_pgtable *pgt = ctx->arg;
        struct kvm_pgtable_mm_ops *mm_ops = pgt->mm_ops;
-       kvm_pte_t pte = *ptep;
 
-       if (!kvm_pte_valid(pte) || !stage2_pte_cacheable(pgt, pte))
+       if (!kvm_pte_valid(ctx->old) || !stage2_pte_cacheable(pgt, ctx->old))
                return 0;
 
        if (mm_ops->dcache_clean_inval_poc)
-               mm_ops->dcache_clean_inval_poc(kvm_pte_follow(pte, mm_ops),
-                                              kvm_granule_size(level));
+               mm_ops->dcache_clean_inval_poc(kvm_pte_follow(ctx->old, mm_ops),
+                                              kvm_granule_size(ctx->level));
        return 0;
 }
 
@@ -1172,7 +1189,7 @@ int __kvm_pgtable_stage2_init(struct kvm_pgtable *pgt, struct kvm_s2_mmu *mmu,
        u32 start_level = VTCR_EL2_TGRAN_SL0_BASE - sl0;
 
        pgd_sz = kvm_pgd_pages(ia_bits, start_level) * PAGE_SIZE;
-       pgt->pgd = mm_ops->zalloc_pages_exact(pgd_sz);
+       pgt->pgd = (kvm_pteref_t)mm_ops->zalloc_pages_exact(pgd_sz);
        if (!pgt->pgd)
                return -ENOMEM;
 
@@ -1197,20 +1214,18 @@ size_t kvm_pgtable_stage2_pgd_size(u64 vtcr)
        return kvm_pgd_pages(ia_bits, start_level) * PAGE_SIZE;
 }
 
-static int stage2_free_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
-                             enum kvm_pgtable_walk_flags flag,
-                             void * const arg)
+static int stage2_free_walker(const struct kvm_pgtable_visit_ctx *ctx,
+                             enum kvm_pgtable_walk_flags visit)
 {
-       struct kvm_pgtable_mm_ops *mm_ops = arg;
-       kvm_pte_t pte = *ptep;
+       struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
 
-       if (!stage2_pte_is_counted(pte))
+       if (!stage2_pte_is_counted(ctx->old))
                return 0;
 
-       mm_ops->put_page(ptep);
+       mm_ops->put_page(ctx->ptep);
 
-       if (kvm_pte_table(pte, level))
-               mm_ops->put_page(kvm_pte_follow(pte, mm_ops));
+       if (kvm_pte_table(ctx->old, ctx->level))
+               mm_ops->put_page(kvm_pte_follow(ctx->old, mm_ops));
 
        return 0;
 }
@@ -1222,11 +1237,33 @@ void kvm_pgtable_stage2_destroy(struct kvm_pgtable *pgt)
                .cb     = stage2_free_walker,
                .flags  = KVM_PGTABLE_WALK_LEAF |
                          KVM_PGTABLE_WALK_TABLE_POST,
-               .arg    = pgt->mm_ops,
        };
 
        WARN_ON(kvm_pgtable_walk(pgt, 0, BIT(pgt->ia_bits), &walker));
        pgd_sz = kvm_pgd_pages(pgt->ia_bits, pgt->start_level) * PAGE_SIZE;
-       pgt->mm_ops->free_pages_exact(pgt->pgd, pgd_sz);
+       pgt->mm_ops->free_pages_exact(kvm_dereference_pteref(&walker, pgt->pgd), pgd_sz);
        pgt->pgd = NULL;
 }
+
+void kvm_pgtable_stage2_free_removed(struct kvm_pgtable_mm_ops *mm_ops, void *pgtable, u32 level)
+{
+       kvm_pteref_t ptep = (kvm_pteref_t)pgtable;
+       struct kvm_pgtable_walker walker = {
+               .cb     = stage2_free_walker,
+               .flags  = KVM_PGTABLE_WALK_LEAF |
+                         KVM_PGTABLE_WALK_TABLE_POST,
+       };
+       struct kvm_pgtable_walk_data data = {
+               .walker = &walker,
+
+               /*
+                * At this point the IPA really doesn't matter, as the page
+                * table being traversed has already been removed from the stage
+                * 2. Set an appropriate range to cover the entire page table.
+                */
+               .addr   = 0,
+               .end    = kvm_granule_size(level),
+       };
+
+       WARN_ON(__kvm_pgtable_walk(&data, mm_ops, ptep, level + 1));
+}