[PATCH v2 6/6] KVM: arm64: ptdump: Introduce the shadow ptdump file
Wei-Lin Chang
weilin.chang at arm.com
Fri Jul 3 03:27:47 PDT 2026
On Fri, Jul 03, 2026 at 06:48:07AM +0900, Itaru Kitayama wrote:
> On Tue, Jun 30, 2026 at 01:10:05PM +0100, Wei-Lin Chang wrote:
> > Create a ptdump file for all shadow page tables. It will dump out all
> > valid shadow page tables at the time of request, with the mmu's index,
> > guest VTCR_EL2, VTTBR_EL2, and whether the guest stage-2 is enabled or
> > not.
> >
> > Also detach the nested mmu array under the mmu_lock in
> > kvm_arch_flush_shadow_all() so readers cannot race with the array being
> > removed, then free the old array after dropping the lock.
> >
> > Signed-off-by: Wei-Lin Chang <weilin.chang at arm.com>
> > ---
> > arch/arm64/kvm/nested.c | 12 ++++++--
> > arch/arm64/kvm/ptdump.c | 61 ++++++++++++++++++++++++++++++++++++++++-
> > 2 files changed, 69 insertions(+), 4 deletions(-)
> >
> > diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
> > index 6435efd65cb5..17a180ddf6ca 100644
> > --- a/arch/arm64/kvm/nested.c
> > +++ b/arch/arm64/kvm/nested.c
> > @@ -1283,6 +1283,7 @@ void kvm_nested_s2_flush(struct kvm *kvm)
> >
> > void kvm_arch_flush_shadow_all(struct kvm *kvm)
> > {
> > + struct kvm_s2_mmu *mmus;
> > int i;
> >
> > for (i = 0; i < kvm->arch.nested_mmus_size; i++) {
> > @@ -1291,9 +1292,14 @@ void kvm_arch_flush_shadow_all(struct kvm *kvm)
> > if (!WARN_ON(atomic_read(&mmu->refcnt)))
> > kvm_free_stage2_pgd(mmu);
> > }
> > - kvfree(kvm->arch.nested_mmus);
> > - kvm->arch.nested_mmus = NULL;
> > - kvm->arch.nested_mmus_size = 0;
> > +
> > + scoped_guard(write_lock, &kvm->mmu_lock) {
> > + mmus = kvm->arch.nested_mmus;
> > + kvm->arch.nested_mmus = NULL;
> > + kvm->arch.nested_mmus_size = 0;
> > + }
> > +
> > + kvfree(mmus);
> > kvm_uninit_stage2_mmu(kvm);
> > }
> >
> > diff --git a/arch/arm64/kvm/ptdump.c b/arch/arm64/kvm/ptdump.c
> > index 40f93b7c7ad9..1649eaa75798 100644
> > --- a/arch/arm64/kvm/ptdump.c
> > +++ b/arch/arm64/kvm/ptdump.c
> > @@ -181,6 +181,50 @@ static int kvm_ptdump_guest_canonical_show(struct seq_file *m, void *unused)
> > return 0;
> > }
> >
> > +static int kvm_ptdump_guest_nested_show(struct seq_file *m, void *unused)
> > +{
> > + int ret = 0, i;
> > + struct kvm_ptdump_guest_state *st = m->private;
> > + struct kvm *kvm = st->kvm;
> > + struct kvm_pgtable_walker walker = (struct kvm_pgtable_walker) {
> > + .cb = kvm_ptdump_visitor,
> > + .arg = &st->parser_state,
> > + .flags = KVM_PGTABLE_WALK_LEAF,
> > + };
> > +
> > + guard(write_lock)(&kvm->mmu_lock);
> > +
> > + if (!kvm->arch.nested_mmus)
> > + return 0;
> > +
> > + for (i = 0; i < kvm->arch.nested_mmus_size; i++) {
> > + struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i];
> > +
> > + if (!mmu->pgt)
> > + continue;
> > +
> > + if (kvm_s2_mmu_valid(mmu)) {
> > + memset(st, 0, sizeof(*st));
> > + ret = kvm_ptdump_parser_init(st, kvm, mmu->pgt);
> > + if (ret)
> > + return ret;
> > + st->parser_state = (struct ptdump_pg_state) {
> > + .marker = &st->ipa_marker[0],
> > + .level = -1,
> > + .pg_level = &st->level[0],
> > + .seq = m,
> > + };
> > + seq_printf(m, "nested mmu %d VTCR: 0x%016llx VTTBR: 0x%016llx s2: %s\n",
> > + i, mmu->tlb_vtcr, mmu->tlb_vttbr,
> > + mmu->nested_stage2_enabled ? "enabled" : "disabled");
>
> This header information in the debugfs "shadow_page_tables" file, under
> the nested directory is showing guest hypervisor's configuration while
> the file is designed to show nested guest's shadwo stage 2 translation
> tables layouts owned by Host EL2 hypervisor. Is this intentional?
Yes, this is intentional. Here we are exposing guest hypervisor VTCR,
VTTBR, s2 enabled or not, because that is how the host hypervisor
distinguishes between different nested mmus (lookup_s2_mmu()).
Thanks,
Wei-Lin Chang
>
> Thanks,
> Itaru.
>
> > + ret = kvm_pgtable_walk(mmu->pgt, 0, BIT(mmu->pgt->ia_bits), &walker);
> > + if (ret)
> > + return ret;
> > + }
> > + }
> > + return ret;
> > +}
> > +
> > static int kvm_ptdump_guest_open(struct inode *m, struct file *file,
> > int (*show)(struct seq_file *, void *))
> > {
> > @@ -212,6 +256,11 @@ static int kvm_ptdump_guest_canonical_open(struct inode *m, struct file *file)
> > return kvm_ptdump_guest_open(m, file, kvm_ptdump_guest_canonical_show);
> > }
> >
> > +static int kvm_ptdump_guest_nested_open(struct inode *m, struct file *file)
> > +{
> > + return kvm_ptdump_guest_open(m, file, kvm_ptdump_guest_nested_show);
> > +}
> > +
> > static int kvm_ptdump_guest_close(struct inode *m, struct file *file)
> > {
> > struct kvm *kvm = m->i_private;
> > @@ -230,6 +279,13 @@ static const struct file_operations kvm_ptdump_guest_canonical_fops = {
> > .release = kvm_ptdump_guest_close,
> > };
> >
> > +static const struct file_operations kvm_ptdump_guest_nested_fops = {
> > + .open = kvm_ptdump_guest_nested_open,
> > + .read = seq_read,
> > + .llseek = seq_lseek,
> > + .release = kvm_ptdump_guest_close,
> > +};
> > +
> > static int kvm_pgtable_range_show(struct seq_file *m, void *unused)
> > {
> > struct kvm *kvm = m->private;
> > @@ -307,6 +363,9 @@ void kvm_s2_ptdump_create_debugfs(struct kvm *kvm)
> > kvm, &kvm_pgtable_range_fops);
> > debugfs_create_file("stage2_levels", 0400, kvm->debugfs_dentry,
> > kvm, &kvm_pgtable_levels_fops);
> > - if (cpus_have_final_cap(ARM64_HAS_NESTED_VIRT))
> > + if (cpus_have_final_cap(ARM64_HAS_NESTED_VIRT)) {
> > kvm->arch.debugfs_nv_dentry = debugfs_create_dir("nested", kvm->debugfs_dentry);
> > + debugfs_create_file("shadow_page_tables", 0400, kvm->arch.debugfs_nv_dentry,
> > + kvm, &kvm_ptdump_guest_nested_fops);
> > + }
> > }
> > --
> > 2.43.0
> >
More information about the linux-arm-kernel
mailing list