* [PATCH v3 1/3] riscv: ptdump: Create ptdump.h and move declarations
2026-07-28 16:14 ` [PATCH v3 0/3] RISC-V KVM gstage page table dumper Dylan.Wu
@ 2026-07-28 16:14 ` Dylan.Wu
2026-07-28 16:14 ` [PATCH v3 2/3] riscv: ptdump: Use per-level attribute bits for parsing Dylan.Wu
2026-07-28 16:14 ` [PATCH v3 3/3] KVM: riscv: Register ptdump with debugfs on guest creation Dylan.Wu
2 siblings, 0 replies; 10+ messages in thread
From: Dylan.Wu @ 2026-07-28 16:14 UTC (permalink / raw)
To: paul.walmsley, pjw
Cc: palmer, aou, alex, anup, atish.patra, linux-riscv, linux-kernel,
kvm-riscv, kvm, Dylan.Wu, Quan Zhou
Create a new arch/riscv/include/asm/ptdump.h header file and move the
pagetable walking state structures and level definitions there. This
allows other parts of the kernel (like KVM) to reuse the ptdump data
structures.
Also export the note_page() symbol so it can be used by other kernel
components.
No functional changes - structures are moved verbatim.
Assisted-by: YuanSheng: deepseek-v4-pro
Co-developed-by: Quan Zhou <zhouquan@iscas.ac.cn>
Signed-off-by: Quan Zhou <zhouquan@iscas.ac.cn>
Signed-off-by: Dylan.Wu <fredwudi0305@gmail.com>
---
arch/riscv/include/asm/ptdump.h | 40 ++++++++++++++++++++++++++++++
arch/riscv/mm/ptdump.c | 44 +++------------------------------
2 files changed, 44 insertions(+), 40 deletions(-)
create mode 100644 arch/riscv/include/asm/ptdump.h
diff --git a/arch/riscv/include/asm/ptdump.h b/arch/riscv/include/asm/ptdump.h
new file mode 100644
index 000000000..90dce9f64
--- /dev/null
+++ b/arch/riscv/include/asm/ptdump.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_RISCV_PTDUMP_H
+#define _ASM_RISCV_PTDUMP_H
+
+#include <linux/ptdump.h>
+#include <linux/seq_file.h>
+
+struct addr_marker {
+ unsigned long start_address;
+ const char *name;
+};
+
+struct prot_bits {
+ u64 mask;
+ const char *set;
+ const char *clear;
+};
+
+struct pg_level {
+ const char *name;
+ u64 mask;
+};
+
+struct pg_state {
+ struct ptdump_state ptdump;
+ struct seq_file *seq;
+ const struct addr_marker *marker;
+ unsigned long start_address;
+ unsigned long start_pa;
+ unsigned long last_pa;
+ int level;
+ u64 current_prot;
+ bool check_wx;
+ unsigned long wx_pages;
+};
+
+void note_page(struct ptdump_state *pt_st, unsigned long addr,
+ int level, u64 val);
+
+#endif /* _ASM_RISCV_PTDUMP_H */
diff --git a/arch/riscv/mm/ptdump.c b/arch/riscv/mm/ptdump.c
index f4b4a9fcb..d9a955b8a 100644
--- a/arch/riscv/mm/ptdump.c
+++ b/arch/riscv/mm/ptdump.c
@@ -11,6 +11,7 @@
#include <linux/ptdump.h>
#include <linux/pgtable.h>
+#include <asm/ptdump.h>
#include <asm/kasan.h>
#define pt_dump_seq_printf(m, fmt, args...) \
@@ -25,31 +26,6 @@
seq_puts(m, fmt); \
})
-/*
- * The page dumper groups page table entries of the same type into a single
- * description. It uses pg_state to track the range information while
- * iterating over the pte entries. When the continuity is broken it then
- * dumps out a description of the range.
- */
-struct pg_state {
- struct ptdump_state ptdump;
- struct seq_file *seq;
- const struct addr_marker *marker;
- unsigned long start_address;
- unsigned long start_pa;
- unsigned long last_pa;
- int level;
- u64 current_prot;
- bool check_wx;
- unsigned long wx_pages;
-};
-
-/* Address marker */
-struct addr_marker {
- unsigned long start_address;
- const char *name;
-};
-
/* Private information for debugfs */
struct ptd_mm_info {
struct mm_struct *mm;
@@ -126,13 +102,6 @@ static struct ptd_mm_info efi_ptd_info = {
};
#endif
-/* Page Table Entry */
-struct prot_bits {
- u64 mask;
- const char *set;
- const char *clear;
-};
-
static const struct prot_bits pte_bits[] = {
{
#ifdef CONFIG_64BIT
@@ -183,12 +152,6 @@ static const struct prot_bits pte_bits[] = {
}
};
-/* Page Level */
-struct pg_level {
- const char *name;
- u64 mask;
-};
-
static struct pg_level pg_level[] = {
{ /* pgd */
.name = "PGD",
@@ -276,8 +239,8 @@ static void note_prot_wx(struct pg_state *st, unsigned long addr)
st->wx_pages += (addr - st->start_address) / PAGE_SIZE;
}
-static void note_page(struct ptdump_state *pt_st, unsigned long addr,
- int level, u64 val)
+void note_page(struct ptdump_state *pt_st, unsigned long addr,
+ int level, u64 val)
{
struct pg_state *st = container_of(pt_st, struct pg_state, ptdump);
u64 pa = PFN_PHYS(pte_pfn(__pte(val)));
@@ -317,6 +280,7 @@ static void note_page(struct ptdump_state *pt_st, unsigned long addr,
st->last_pa = pa;
}
}
+EXPORT_SYMBOL_GPL(note_page);
static void note_page_pte(struct ptdump_state *pt_st, unsigned long addr, pte_t pte)
{
--
2.34.1
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v3 2/3] riscv: ptdump: Use per-level attribute bits for parsing
2026-07-28 16:14 ` [PATCH v3 0/3] RISC-V KVM gstage page table dumper Dylan.Wu
2026-07-28 16:14 ` [PATCH v3 1/3] riscv: ptdump: Create ptdump.h and move declarations Dylan.Wu
@ 2026-07-28 16:14 ` Dylan.Wu
2026-07-28 16:14 ` [PATCH v3 3/3] KVM: riscv: Register ptdump with debugfs on guest creation Dylan.Wu
2 siblings, 0 replies; 10+ messages in thread
From: Dylan.Wu @ 2026-07-28 16:14 UTC (permalink / raw)
To: paul.walmsley, pjw
Cc: palmer, aou, alex, anup, atish.patra, linux-riscv, linux-kernel,
kvm-riscv, kvm, Dylan.Wu, Quan Zhou
Rename the ptdump data structures with a ptdump_ prefix for clarity
and to avoid namespace collisions when other modules (like KVM) define
their own instances:
- struct prot_bits -> struct ptdump_prot_bits
- struct pg_level -> struct ptdump_pg_level
- struct pg_state -> struct ptdump_pg_state
Add bits/num fields to struct ptdump_pg_level so each page table level
can have its own set of attribute bits, and add a pg_level pointer to
struct ptdump_pg_state so the attribute parsing logic can use per-level
bits instead of a global array.
Update dump_prot() to use the per-level bits from the pg_level pointer
rather than the global pte_bits array. Rename the pg_level[] instance
to kernel_pg_levels[] to distinguish it from future gstage instances.
No functional changes in the output.
Assisted-by: YuanSheng: deepseek-v4-pro
Co-developed-by: Quan Zhou <zhouquan@iscas.ac.cn>
Signed-off-by: Quan Zhou <zhouquan@iscas.ac.cn>
Signed-off-by: Dylan.Wu <fredwudi0305@gmail.com>
---
arch/riscv/include/asm/ptdump.h | 9 +++--
arch/riscv/mm/ptdump.c | 62 ++++++++++++++++++++-------------
2 files changed, 44 insertions(+), 27 deletions(-)
diff --git a/arch/riscv/include/asm/ptdump.h b/arch/riscv/include/asm/ptdump.h
index 90dce9f64..53b0e52f0 100644
--- a/arch/riscv/include/asm/ptdump.h
+++ b/arch/riscv/include/asm/ptdump.h
@@ -10,18 +10,20 @@ struct addr_marker {
const char *name;
};
-struct prot_bits {
+struct ptdump_prot_bits {
u64 mask;
const char *set;
const char *clear;
};
-struct pg_level {
+struct ptdump_pg_level {
+ const struct ptdump_prot_bits *bits;
+ size_t num;
const char *name;
u64 mask;
};
-struct pg_state {
+struct ptdump_pg_state {
struct ptdump_state ptdump;
struct seq_file *seq;
const struct addr_marker *marker;
@@ -32,6 +34,7 @@ struct pg_state {
u64 current_prot;
bool check_wx;
unsigned long wx_pages;
+ const struct ptdump_pg_level *pg_level;
};
void note_page(struct ptdump_state *pt_st, unsigned long addr,
diff --git a/arch/riscv/mm/ptdump.c b/arch/riscv/mm/ptdump.c
index d9a955b8a..c67fbbf5f 100644
--- a/arch/riscv/mm/ptdump.c
+++ b/arch/riscv/mm/ptdump.c
@@ -102,7 +102,7 @@ static struct ptd_mm_info efi_ptd_info = {
};
#endif
-static const struct prot_bits pte_bits[] = {
+static const struct ptdump_prot_bits pte_bits[] = {
{
#ifdef CONFIG_64BIT
.mask = _PAGE_NAPOT,
@@ -152,46 +152,58 @@ static const struct prot_bits pte_bits[] = {
}
};
-static struct pg_level pg_level[] = {
+static struct ptdump_pg_level kernel_pg_levels[] = {
{ /* pgd */
+ .bits = pte_bits,
+ .num = ARRAY_SIZE(pte_bits),
.name = "PGD",
}, { /* p4d */
+ .bits = pte_bits,
+ .num = ARRAY_SIZE(pte_bits),
.name = (CONFIG_PGTABLE_LEVELS > 4) ? "P4D" : "PGD",
}, { /* pud */
+ .bits = pte_bits,
+ .num = ARRAY_SIZE(pte_bits),
.name = (CONFIG_PGTABLE_LEVELS > 3) ? "PUD" : "PGD",
}, { /* pmd */
+ .bits = pte_bits,
+ .num = ARRAY_SIZE(pte_bits),
.name = (CONFIG_PGTABLE_LEVELS > 2) ? "PMD" : "PGD",
}, { /* pte */
+ .bits = pte_bits,
+ .num = ARRAY_SIZE(pte_bits),
.name = "PTE",
},
};
-static void dump_prot(struct pg_state *st)
+static void dump_prot(struct ptdump_pg_state *st)
{
+ const struct ptdump_pg_level *lvl = &st->pg_level[st->level];
+ const struct ptdump_prot_bits *bits = lvl->bits;
unsigned int i;
- for (i = 0; i < ARRAY_SIZE(pte_bits); i++) {
+ for (i = 0; i < lvl->num; i++) {
char s[7];
unsigned long val;
- val = st->current_prot & pte_bits[i].mask;
+ val = st->current_prot & bits[i].mask;
if (val) {
- if (pte_bits[i].mask == _PAGE_SOFT)
- snprintf(s, sizeof(s), pte_bits[i].set, val >> 8);
+ if (bits[i].mask == _PAGE_SOFT)
+ snprintf(s, sizeof(s), bits[i].set, val >> 8);
#ifdef CONFIG_64BIT
- else if (pte_bits[i].mask == _PAGE_MTMASK_SVPBMT) {
+ else if (bits[i].mask == _PAGE_MTMASK_SVPBMT) {
if (val == _PAGE_NOCACHE_SVPBMT)
- snprintf(s, sizeof(s), pte_bits[i].set, "NC");
+ snprintf(s, sizeof(s), bits[i].set, "NC");
else if (val == _PAGE_IO_SVPBMT)
- snprintf(s, sizeof(s), pte_bits[i].set, "IO");
+ snprintf(s, sizeof(s), bits[i].set, "IO");
else
- snprintf(s, sizeof(s), pte_bits[i].set, "??");
+ snprintf(s, sizeof(s), bits[i].set, "??");
}
#endif
else
- strscpy(s, pte_bits[i].set);
+ strscpy(s, bits[i].set);
} else {
- strscpy(s, pte_bits[i].clear);
+ strscpy(s, bits[i].clear);
}
pt_dump_seq_printf(st->seq, " %s", s);
@@ -203,7 +215,7 @@ static void dump_prot(struct pg_state *st)
#else
#define ADDR_FORMAT "0x%08lx"
#endif
-static void dump_addr(struct pg_state *st, unsigned long addr)
+static void dump_addr(struct ptdump_pg_state *st, unsigned long addr)
{
static const char units[] = "KMGTPE";
const char *unit = units;
@@ -221,10 +233,10 @@ static void dump_addr(struct pg_state *st, unsigned long addr)
}
pt_dump_seq_printf(st->seq, "%9lu%c %s", delta, *unit,
- pg_level[st->level].name);
+ kernel_pg_levels[st->level].name);
}
-static void note_prot_wx(struct pg_state *st, unsigned long addr)
+static void note_prot_wx(struct ptdump_pg_state *st, unsigned long addr)
{
if (!st->check_wx)
return;
@@ -242,12 +254,12 @@ static void note_prot_wx(struct pg_state *st, unsigned long addr)
void note_page(struct ptdump_state *pt_st, unsigned long addr,
int level, u64 val)
{
- struct pg_state *st = container_of(pt_st, struct pg_state, ptdump);
+ struct ptdump_pg_state *st = container_of(pt_st, struct ptdump_pg_state, ptdump);
u64 pa = PFN_PHYS(pte_pfn(__pte(val)));
u64 prot = 0;
if (level >= 0)
- prot = val & pg_level[level].mask;
+ prot = val & kernel_pg_levels[level].mask;
if (st->level == -1) {
st->level = level;
@@ -316,10 +328,11 @@ static void note_page_flush(struct ptdump_state *pt_st)
static void ptdump_walk(struct seq_file *s, struct ptd_mm_info *pinfo)
{
- struct pg_state st = {
+ struct ptdump_pg_state st = {
.seq = s,
.marker = pinfo->markers,
.level = -1,
+ .pg_level = kernel_pg_levels,
.ptdump = {
.note_page_pte = note_page_pte,
.note_page_pmd = note_page_pmd,
@@ -339,13 +352,14 @@ static void ptdump_walk(struct seq_file *s, struct ptd_mm_info *pinfo)
bool ptdump_check_wx(void)
{
- struct pg_state st = {
+ struct ptdump_pg_state st = {
.seq = NULL,
.marker = (struct addr_marker[]) {
{0, NULL},
{-1, NULL},
},
.level = -1,
+ .pg_level = kernel_pg_levels,
.check_wx = true,
.ptdump = {
.note_page_pte = note_page_pte,
@@ -410,12 +424,12 @@ static int __init ptdump_init(void)
kernel_ptd_info.base_addr = KERN_VIRT_START;
- pg_level[1].name = pgtable_l5_enabled ? "P4D" : "PGD";
- pg_level[2].name = pgtable_l4_enabled ? "PUD" : "PGD";
+ kernel_pg_levels[1].name = pgtable_l5_enabled ? "P4D" : "PGD";
+ kernel_pg_levels[2].name = pgtable_l4_enabled ? "PUD" : "PGD";
- for (i = 0; i < ARRAY_SIZE(pg_level); i++)
+ for (i = 0; i < ARRAY_SIZE(kernel_pg_levels); i++)
for (j = 0; j < ARRAY_SIZE(pte_bits); j++)
- pg_level[i].mask |= pte_bits[j].mask;
+ kernel_pg_levels[i].mask |= pte_bits[j].mask;
debugfs_create_file("kernel_page_tables", 0400, NULL, &kernel_ptd_info,
&ptdump_fops);
--
2.34.1
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v3 3/3] KVM: riscv: Register ptdump with debugfs on guest creation
2026-07-28 16:14 ` [PATCH v3 0/3] RISC-V KVM gstage page table dumper Dylan.Wu
2026-07-28 16:14 ` [PATCH v3 1/3] riscv: ptdump: Create ptdump.h and move declarations Dylan.Wu
2026-07-28 16:14 ` [PATCH v3 2/3] riscv: ptdump: Use per-level attribute bits for parsing Dylan.Wu
@ 2026-07-28 16:14 ` Dylan.Wu
2026-10-02 6:43 ` Anup Patel
2 siblings, 1 reply; 10+ messages in thread
From: Dylan.Wu @ 2026-07-28 16:14 UTC (permalink / raw)
To: paul.walmsley, pjw
Cc: palmer, aou, alex, anup, atish.patra, linux-riscv, linux-kernel,
kvm-riscv, kvm, Dylan.Wu, Quan Zhou
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@iscas.ac.cn>
Signed-off-by: Quan Zhou <zhouquan@iscas.ac.cn>
Signed-off-by: Dylan.Wu <fredwudi0305@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",
+ },
+};
+
+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
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH v3 3/3] KVM: riscv: Register ptdump with debugfs on guest creation
2026-07-28 16:14 ` [PATCH v3 3/3] KVM: riscv: Register ptdump with debugfs on guest creation Dylan.Wu
@ 2026-10-02 6:43 ` Anup Patel
0 siblings, 0 replies; 10+ messages in thread
From: Anup Patel @ 2026-10-02 6:43 UTC (permalink / raw)
To: Dylan.Wu
Cc: paul.walmsley, pjw, palmer, aou, alex, atish.patra, linux-riscv,
linux-kernel, kvm-riscv, kvm, Quan Zhou
On Tue, Jul 28, 2026 at 9:45 PM Dylan.Wu <fredwudi0305@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@iscas.ac.cn>
> Signed-off-by: Quan Zhou <zhouquan@iscas.ac.cn>
> Signed-off-by: Dylan.Wu <fredwudi0305@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
^ permalink raw reply [flat|nested] 10+ messages in thread