Commit 156f70af authored by Oliver Upton's avatar Oliver Upton
Browse files

KVM: arm64: Only drop references on empty tables in stage2_free_walker

A subsequent change to the way KVM frees stage-2s will invoke the free
walker on sub-ranges of the VM's IPA space, meaning there's potential
for only partially visiting a table's PTEs.

Split the leaf and table visitors and only drop references on a table
when the page count reaches 1, implying there are no valid PTEs that
need to be visited. Invalidate the table PTE to avoid traversing the
stale reference.

Link: https://msgid.link/20251113052452.975081-2-rananta@google.com


Signed-off-by: default avatarOliver Upton <oupton@kernel.org>
parent 85f329df
Loading
Loading
Loading
Loading
+32 −6
Original line number Diff line number Diff line
@@ -1535,22 +1535,48 @@ size_t kvm_pgtable_stage2_pgd_size(u64 vtcr)
	return kvm_pgd_pages(ia_bits, start_level) * PAGE_SIZE;
}

static int stage2_free_walker(const struct kvm_pgtable_visit_ctx *ctx,
			      enum kvm_pgtable_walk_flags visit)
static int stage2_free_leaf(const struct kvm_pgtable_visit_ctx *ctx)
{
	struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;

	if (!stage2_pte_is_counted(ctx->old))
	mm_ops->put_page(ctx->ptep);
	return 0;
}

	mm_ops->put_page(ctx->ptep);
static int stage2_free_table_post(const struct kvm_pgtable_visit_ctx *ctx)
{
	struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
	kvm_pte_t *childp = kvm_pte_follow(ctx->old, mm_ops);

	if (kvm_pte_table(ctx->old, ctx->level))
		mm_ops->put_page(kvm_pte_follow(ctx->old, mm_ops));
	if (mm_ops->page_count(childp) != 1)
		return 0;

	/*
	 * Drop references and clear the now stale PTE to avoid rewalking the
	 * freed page table.
	 */
	mm_ops->put_page(ctx->ptep);
	mm_ops->put_page(childp);
	kvm_clear_pte(ctx->ptep);
	return 0;
}

static int stage2_free_walker(const struct kvm_pgtable_visit_ctx *ctx,
			      enum kvm_pgtable_walk_flags visit)
{
	if (!stage2_pte_is_counted(ctx->old))
		return 0;

	switch (visit) {
	case KVM_PGTABLE_WALK_LEAF:
		return stage2_free_leaf(ctx);
	case KVM_PGTABLE_WALK_TABLE_POST:
		return stage2_free_table_post(ctx);
	default:
		return -EINVAL;
	}
}

void kvm_pgtable_stage2_destroy(struct kvm_pgtable *pgt)
{
	size_t pgd_sz;