[PATCH v3 3/3] KVM: riscv: Register ptdump with debugfs on guest creation
Anup Patel
anup at brainfault.org
Thu Oct 1 23:43:43 PDT 2026
On Tue, Jul 28, 2026 at 9:45 PM Dylan.Wu <fredwudi0305 at gmail.com> wrote:
>
> Implement the gstage page table dumper for RISC-V KVM, which exposes
> the guest's gstage page tables via debugfs. When
> CONFIG_PTDUMP_GSTAGE_DEBUGFS is enabled, a "gstage_page_tables" file
> will be created under each VM's debugfs directory.
>
> This reuses the ptdump framework from the kernel page table dumper,
> with gstage-specific attribute bits and level definitions.
>
> Assisted-by: YuanSheng: deepseek-v4-pro
> Co-developed-by: Quan Zhou <zhouquan at iscas.ac.cn>
> Signed-off-by: Quan Zhou <zhouquan at iscas.ac.cn>
> Signed-off-by: Dylan.Wu <fredwudi0305 at gmail.com>
> ---
> arch/riscv/kvm/Kconfig | 12 +++
> arch/riscv/kvm/Makefile | 1 +
> arch/riscv/kvm/ptdump.c | 177 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 190 insertions(+)
> create mode 100644 arch/riscv/kvm/ptdump.c
>
> diff --git a/arch/riscv/kvm/Kconfig b/arch/riscv/kvm/Kconfig
> index ec2cee0a3..6d62b967f 100644
> --- a/arch/riscv/kvm/Kconfig
> +++ b/arch/riscv/kvm/Kconfig
> @@ -37,4 +37,16 @@ config KVM
>
> If unsure, say N.
>
> +config PTDUMP_GSTAGE_DEBUGFS
> + bool "Present the gstage pagetables to debugfs"
> + depends on KVM && DEBUG_KERNEL && DEBUG_FS && PTDUMP_DEBUGFS
> + default n
> + help
> + Say Y here if you want to show the RISC-V KVM gstage guest
> + page tables in debugfs under each VM's debugfs directory as
> + a file named "gstage_page_tables". This allows inspection of
> + the second-stage address translation setup for each guest.
> +
> + If unsure, say N.
> +
> endif # VIRTUALIZATION
> diff --git a/arch/riscv/kvm/Makefile b/arch/riscv/kvm/Makefile
> index 296c2ba05..905593d6f 100644
> --- a/arch/riscv/kvm/Makefile
> +++ b/arch/riscv/kvm/Makefile
> @@ -19,6 +19,7 @@ kvm-y += isa.o
> kvm-y += main.o
> kvm-y += mmu.o
> kvm-y += nacl.o
> +kvm-$(CONFIG_PTDUMP_GSTAGE_DEBUGFS) += ptdump.o
> kvm-y += tlb.o
> kvm-y += vcpu.o
> kvm-y += vcpu_config.o
> diff --git a/arch/riscv/kvm/ptdump.c b/arch/riscv/kvm/ptdump.c
> new file mode 100644
> index 000000000..f436ad48c
> --- /dev/null
> +++ b/arch/riscv/kvm/ptdump.c
> @@ -0,0 +1,177 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2024 SiFive
> + */
> +
> +#include <linux/debugfs.h>
> +#include <linux/ptdump.h>
> +#include <linux/seq_file.h>
> +
> +#include <linux/kvm_host.h>
> +#include <asm/kvm_gstage.h>
> +#include <asm/pgtable.h>
> +#include <asm/ptdump.h>
> +
> +enum gstage_address_markers_idx {
> + GSTAGE_GPA_START_NR,
> + GSTAGE_GPA_END_NR,
> + END_OF_GSTAGE_SPACE_NR
> +};
> +
> +static const struct ptdump_prot_bits gstage_pte_bits[] = {
> + {
> + .mask = _PAGE_DIRTY,
> + .set = "D",
> + .clear = ".",
> + }, {
> + .mask = _PAGE_ACCESSED,
> + .set = "A",
> + .clear = ".",
> + }, {
> + .mask = _PAGE_GLOBAL,
> + .set = "G",
> + .clear = ".",
> + }, {
> + .mask = _PAGE_USER,
> + .set = "U",
> + .clear = ".",
> + }, {
> + .mask = _PAGE_EXEC,
> + .set = "X",
> + .clear = ".",
> + }, {
> + .mask = _PAGE_WRITE,
> + .set = "W",
> + .clear = ".",
> + }, {
> + .mask = _PAGE_READ,
> + .set = "R",
> + .clear = ".",
> + }, {
> + .mask = _PAGE_PRESENT,
> + .set = "V",
> + .clear = ".",
> + }
> +};
> +
> +static struct ptdump_pg_level gstage_pg_levels[] = {
> + { /* pgd */
> + .bits = gstage_pte_bits,
> + .num = ARRAY_SIZE(gstage_pte_bits),
> + .name = "PGD",
> + }, { /* p4d */
> + .bits = gstage_pte_bits,
> + .num = ARRAY_SIZE(gstage_pte_bits),
> + .name = "P4D",
> + }, { /* pud */
> + .bits = gstage_pte_bits,
> + .num = ARRAY_SIZE(gstage_pte_bits),
> + .name = "PUD",
> + }, { /* pmd */
> + .bits = gstage_pte_bits,
> + .num = ARRAY_SIZE(gstage_pte_bits),
> + .name = "PMD",
> + }, { /* pte */
> + .bits = gstage_pte_bits,
> + .num = ARRAY_SIZE(gstage_pte_bits),
> + .name = "PTE",
> + },
> +};
As Sashiko pointed, this gstage dumper is already broken
because:
1) We cannot assume that all tables at all levels of G-stage are
of same size. For example, the root G-stage table is 16KB and
not 4KB
2) Number of page table levels can vary between two Guests/VMs
since we allow KVM user-space to change the number GPA bits
which in-turn changes the number of G-stage levels.
> +
> +static void gstage_note_page(struct ptdump_state *pt_st, unsigned long addr,
> + int level, u64 val)
> +{
> + struct ptdump_pg_state *st = container_of(pt_st, struct ptdump_pg_state, ptdump);
> +
> + note_page(pt_st, addr, level, val);
> +
> + if (level >= 0)
> + st->current_prot = val & gstage_pg_levels[level].mask;
> +}
> +
> +static void gstage_note_page_pte(struct ptdump_state *pt_st, unsigned long addr, pte_t pte)
> +{
> + gstage_note_page(pt_st, addr, 4, pte_val(pte));
> +}
> +
> +static void gstage_note_page_pmd(struct ptdump_state *pt_st, unsigned long addr, pmd_t pmd)
> +{
> + gstage_note_page(pt_st, addr, 3, pmd_val(pmd));
> +}
> +
> +static void gstage_note_page_pud(struct ptdump_state *pt_st, unsigned long addr, pud_t pud)
> +{
> + gstage_note_page(pt_st, addr, 2, pud_val(pud));
> +}
> +
> +static void gstage_note_page_p4d(struct ptdump_state *pt_st, unsigned long addr, p4d_t p4d)
> +{
> + gstage_note_page(pt_st, addr, 1, p4d_val(p4d));
> +}
> +
> +static void gstage_note_page_pgd(struct ptdump_state *pt_st, unsigned long addr, pgd_t pgd)
> +{
> + gstage_note_page(pt_st, addr, 0, pgd_val(pgd));
> +}
> +
> +static void gstage_note_page_flush(struct ptdump_state *pt_st)
> +{
> + pte_t pte_zero = {0};
> +
> + gstage_note_page(pt_st, 0, -1, pte_val(pte_zero));
> +}
> +
> +static int gstage_ptdump_show(struct seq_file *m, void *v)
> +{
> + struct kvm *kvm = m->private;
> + gpa_t gpa_size = kvm_riscv_gstage_gpa_size(kvm->arch.pgd_levels);
> + struct addr_marker gpa_markers[] = {
> + [GSTAGE_GPA_START_NR] = { 0, "Guest Physical Address Start" },
> + [GSTAGE_GPA_END_NR] = { gpa_size - 1, "Guest Physical Address End" },
> + [END_OF_GSTAGE_SPACE_NR] = { -1, NULL },
> + };
> + struct ptdump_pg_state st = {
> + .seq = m,
> + .marker = gpa_markers,
> + .level = -1,
> + .pg_level = gstage_pg_levels,
> + .ptdump = {
> + .note_page_pte = gstage_note_page_pte,
> + .note_page_pmd = gstage_note_page_pmd,
> + .note_page_pud = gstage_note_page_pud,
> + .note_page_p4d = gstage_note_page_p4d,
> + .note_page_pgd = gstage_note_page_pgd,
> + .note_page_flush = gstage_note_page_flush,
> + .range = (struct ptdump_range[]) {
> + {0, gpa_size - 1},
> + {0, 0}
> + }
> + }
> + };
> + unsigned int i, j;
> +
> + for (i = 0; i < ARRAY_SIZE(gstage_pg_levels); i++) {
> + gstage_pg_levels[i].mask = 0;
> + for (j = 0; j < ARRAY_SIZE(gstage_pte_bits); j++)
> + gstage_pg_levels[i].mask |= gstage_pte_bits[j].mask;
> + }
> +
> + if (kvm->arch.pgd_levels < 5)
> + gstage_pg_levels[1].name = "PGD";
> + if (kvm->arch.pgd_levels < 4)
> + gstage_pg_levels[2].name = "PGD";
> + if (kvm->arch.pgd_levels < 3)
> + gstage_pg_levels[3].name = "PGD";
> +
> + ptdump_walk_pgd(&st.ptdump, kvm->mm, kvm->arch.pgd);
> +
> + return 0;
> +}
> +
> +DEFINE_SHOW_ATTRIBUTE(gstage_ptdump);
> +
> +void kvm_arch_create_vm_debugfs(struct kvm *kvm)
> +{
> + debugfs_create_file("gstage_page_tables", 0400,
> + kvm->debugfs_dentry, kvm, &gstage_ptdump_fops);
> +}
> --
> 2.34.1
>
Regards,
Anup
More information about the linux-riscv
mailing list