* [PATCH 0/2] riscv: ptdump: Refactor for KVM gstage ptdump support
@ 2026-07-01 8:50 Dylan.Wu
2026-07-01 8:50 ` [PATCH 1/2] riscv: ptdump: Move pagetable definitions to common header Dylan.Wu
2026-07-01 8:50 ` [PATCH 2/2] " Dylan.Wu
0 siblings, 2 replies; 14+ messages in thread
From: Dylan.Wu @ 2026-07-01 8:50 UTC (permalink / raw)
To: palmer, pjw, aou, anup
Cc: alex, atish.patra, zhouquan, linux-riscv, kvm, kvm-riscv,
linux-kernel, Dylan.Wu
This series refactors the RISC-V ptdump implementation to support
dumping KVM gstage (stage-2) page tables via debugfs.
Patch 1 moves the pagetable walking state and level definitions to a
common header (arch/riscv/include/asm/ptdump.h), updates the attribute
parsing logic to use per-level bit definitions, and exports note_page()
so that it can be reused by the KVM ptdump implementation.
Patch 2 introduces KVM gstage ptdump support, registering a
'gstage_page_tables' file under the guest debugfs directory. This
allows userspace to inspect the gstage layout and permissions, which
is useful for architectural debugging and memory management audits.
Testing:
- Booted on QEMU riscv64 with CONFIG_PTDUMP_DEBUGFS=y and
CONFIG_PTDUMP_GSTAGE_DEBUGFS=y
- Verified /sys/kernel/debug/kernel_page_tables output unchanged
- Created a KVM guest and verified gstage_page_tables output:
0x0000000080200000-0x0000000080209000 0x000000010c94e000 36K PTE .. D A U X W R V
0x000000008020a000-0x0000000080223000 0x000000010c9af000 100K PTE .. D A U X W R V
0x0000000080229000-0x000000008022b000 0x000000010c978000 8K PTE .. D A U X W R V
0x000000008022d000-0x000000008022e000 0x000000010c844000 4K PTE .. D A U X W R V
0x000000008022f000-0x0000000080232000 0x000000010c846000 12K PTE .. D A U X W R V
0x0000000080234000-0x0000000080238000 0x000000010ca13000 16K PTE .. D A U X W R V
0x000000008023a000-0x000000008023f000 0x00000001083bd000 20K PTE .. D A U X W R V
0x0000000080242000-0x000000008024b000 0x0000000106975000 36K PTE .. D A U X W R V
0x000000008024d000-0x0000000080252000 0x000000010cb54000 20K PTE .. D A U X W R V
0x0000000080255000-0x0000000080258000 0x000000010c164000 12K PTE .. D A U X W R V
0x000000008025a000-0x000000008025c000 0x00000001036a9000 8K PTE .. D A U X W R V
0x0000000080260000-0x0000000080275000 0x0000000107917000 84K PTE .. D A U X W R V
0x0000000080276000-0x000000008027e000 0x00000001078ed000 32K PTE .. D A U X W R V
0x000000008027f000-0x00000000802bb000 0x0000000108986000 240K PTE .. D A U X W R V
0x00000000802bc000-0x00000000802c0000 0x000000010762b000 16K PTE .. D A U X W R V
0x00000000802c2000-0x00000000802d2000 0x00000001083fd000 64K PTE .. D A U X W R V
0x00000000802d4000-0x00000000802e0000 0x000000010790b000 48K PTE .. D A U X W R V
0x00000000802e1000-0x00000000802e4000 0x0000000102f74000 12K PTE .. D A U X W R V
0x00000000802e5000-0x00000000802ef000 0x0000000104b9c000 40K PTE .. D A U X W R V
0x00000000802f1000-0x00000000802f2000 0x0000000103778000 4K PTE .. D A U X W R V
0x00000000802f3000-0x00000000802f9000 0x000000010377a000 24K PTE .. D A U X W R V
Dylan.Wu (2):
riscv: ptdump: Move pagetable definitions to common header
KVM: riscv: Register ptdump with debugfs on guest creation
arch/riscv/include/asm/kvm_host.h | 6 +
arch/riscv/include/asm/ptdump.h | 42 +++++++
arch/riscv/kvm/Kconfig | 15 +++
arch/riscv/kvm/Makefile | 1 +
arch/riscv/kvm/ptdump.c | 178 ++++++++++++++++++++++++++++++
arch/riscv/kvm/vm.c | 5 +
arch/riscv/mm/ptdump.c | 102 ++++++-----------
7 files changed, 282 insertions(+), 67 deletions(-)
create mode 100644 arch/riscv/include/asm/ptdump.h
create mode 100644 arch/riscv/kvm/ptdump.c
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 1/2] riscv: ptdump: Move pagetable definitions to common header
2026-07-01 8:50 [PATCH 0/2] riscv: ptdump: Refactor for KVM gstage ptdump support Dylan.Wu
@ 2026-07-01 8:50 ` Dylan.Wu
2026-07-01 9:01 ` sashiko-bot
2026-07-21 2:58 ` Paul Walmsley
2026-07-01 8:50 ` [PATCH 2/2] " Dylan.Wu
1 sibling, 2 replies; 14+ messages in thread
From: Dylan.Wu @ 2026-07-01 8:50 UTC (permalink / raw)
To: palmer, pjw, aou, anup
Cc: alex, atish.patra, zhouquan, linux-riscv, kvm, kvm-riscv,
linux-kernel, Dylan.Wu
Move the pagetable walking state and level definitions to a new
arch/riscv/include/asm/ptdump.h header. This allows other parts
of the kernel (like KVM) to reuse the ptdump attribute parsing
logic.
To support different pagetable types (like G-stage), the attribute
parsing logic in dump_prot() is updated to use attribute bits
defined within the pg_level structure rather than a global array.
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 | 42 +++++++++++++
arch/riscv/mm/ptdump.c | 102 +++++++++++---------------------
2 files changed, 77 insertions(+), 67 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..9173910fa
--- /dev/null
+++ b/arch/riscv/include/asm/ptdump.h
@@ -0,0 +1,42 @@
+/* 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 ptdump_prot_bits {
+ u64 mask;
+ const char *set;
+ const char *clear;
+};
+
+struct ptdump_pg_level {
+ const struct ptdump_prot_bits *bits;
+ const char *name;
+ u64 mask;
+ int num;
+};
+
+struct ptdump_pg_state {
+ struct ptdump_state ptdump;
+ struct seq_file *seq;
+ const struct addr_marker *marker;
+ const struct ptdump_pg_level *pg_level;
+ 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..7e55656cb 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,14 +102,7 @@ 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[] = {
+static const struct ptdump_prot_bits pte_bits[] = {
{
#ifdef CONFIG_64BIT
.mask = _PAGE_NAPOT,
@@ -183,52 +152,48 @@ static const struct prot_bits pte_bits[] = {
}
};
-/* Page Level */
-struct pg_level {
- const char *name;
- u64 mask;
-};
-
-static struct pg_level pg_level[] = {
+static struct ptdump_pg_level kernel_pg_levels[] = {
{ /* pgd */
.name = "PGD",
}, { /* p4d */
- .name = (CONFIG_PGTABLE_LEVELS > 4) ? "P4D" : "PGD",
+ .name = "P4D",
}, { /* pud */
- .name = (CONFIG_PGTABLE_LEVELS > 3) ? "PUD" : "PGD",
+ .name = "PUD",
}, { /* pmd */
- .name = (CONFIG_PGTABLE_LEVELS > 2) ? "PMD" : "PGD",
+ .name = "PMD",
}, { /* pte */
.name = "PTE",
},
};
-static void dump_prot(struct pg_state *st)
+static void dump_prot(struct ptdump_pg_state *st)
{
unsigned int i;
+ const struct ptdump_pg_level *lvl = &st->pg_level[st->level];
+ const struct ptdump_prot_bits *bits = lvl->bits;
- 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);
@@ -240,7 +205,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;
@@ -258,10 +223,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);
+ st->pg_level[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;
@@ -276,15 +241,15 @@ 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);
+ 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 & st->pg_level[level].mask;
if (st->level == -1) {
st->level = level;
@@ -317,6 +282,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)
{
@@ -352,9 +318,10 @@ 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,
+ .pg_level = kernel_pg_levels,
.level = -1,
.ptdump = {
.note_page_pte = note_page_pte,
@@ -375,12 +342,13 @@ 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},
},
+ .pg_level = kernel_pg_levels,
.level = -1,
.check_wx = true,
.ptdump = {
@@ -446,12 +414,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 related [flat|nested] 14+ messages in thread
* [PATCH 2/2] KVM: riscv: Register ptdump with debugfs on guest creation
2026-07-01 8:50 [PATCH 0/2] riscv: ptdump: Refactor for KVM gstage ptdump support Dylan.Wu
2026-07-01 8:50 ` [PATCH 1/2] riscv: ptdump: Move pagetable definitions to common header Dylan.Wu
@ 2026-07-01 8:50 ` Dylan.Wu
2026-07-01 9:06 ` sashiko-bot
1 sibling, 1 reply; 14+ messages in thread
From: Dylan.Wu @ 2026-07-01 8:50 UTC (permalink / raw)
To: palmer, pjw, aou, anup
Cc: alex, atish.patra, zhouquan, linux-riscv, kvm, kvm-riscv,
linux-kernel, Dylan.Wu
Introduce KVM ptdump to show the guest gstage pagetables. This registers
a 'gstage_page_tables' file under the guest debugfs directory.
Userspace can now inspect the gstage layout and permissions, which
is useful for architectural debugging and memory management audits.
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/kvm_host.h | 6 +
arch/riscv/kvm/Kconfig | 15 +++
arch/riscv/kvm/Makefile | 1 +
arch/riscv/kvm/ptdump.c | 178 ++++++++++++++++++++++++++++++
arch/riscv/kvm/vm.c | 5 +
5 files changed, 205 insertions(+)
create mode 100644 arch/riscv/kvm/ptdump.c
diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h
index 60017ceec..04129c5f8 100644
--- a/arch/riscv/include/asm/kvm_host.h
+++ b/arch/riscv/include/asm/kvm_host.h
@@ -322,4 +322,10 @@ void kvm_riscv_vcpu_record_steal_time(struct kvm_vcpu *vcpu);
/* Flags representing implementation specific details */
DECLARE_STATIC_KEY_FALSE(kvm_riscv_vsstage_tlb_no_gpa);
+#ifdef CONFIG_PTDUMP_GSTAGE_DEBUGFS
+void kvm_s2_ptdump_create_debugfs(struct kvm *kvm);
+#else
+static inline void kvm_s2_ptdump_create_debugfs(struct kvm *kvm) {}
+#endif
+
#endif /* __RISCV_KVM_HOST_H__ */
diff --git a/arch/riscv/kvm/Kconfig b/arch/riscv/kvm/Kconfig
index ec2cee0a3..0ceb4a452 100644
--- a/arch/riscv/kvm/Kconfig
+++ b/arch/riscv/kvm/Kconfig
@@ -38,3 +38,18 @@ config KVM
If unsure, say N.
endif # VIRTUALIZATION
+
+config PTDUMP_GSTAGE_DEBUGFS
+ bool "Present the gstage pagetables to debugfs"
+ depends on KVM
+ depends on DEBUG_KERNEL
+ depends on DEBUG_FS
+ depends on PTDUMP_DEBUGFS
+ default n
+ help
+ Say Y here if you want to show the RISC-V KVM gstage guest page tables
+ layout in a debugfs file. This information is primarily useful for
+ architecture-specific kernel developers and KVM maintainers to
+ investigate memory mapping and permission issues. It is probably
+ not a good idea to enable this feature in a production kernel.
+ If in doubt, say N.
diff --git a/arch/riscv/kvm/Makefile b/arch/riscv/kvm/Makefile
index 296c2ba05..0170c8c3b 100644
--- a/arch/riscv/kvm/Makefile
+++ b/arch/riscv/kvm/Makefile
@@ -42,3 +42,4 @@ kvm-y += vcpu_timer.o
kvm-y += vcpu_vector.o
kvm-y += vm.o
kvm-y += vmid.o
+kvm-$(CONFIG_PTDUMP_GSTAGE_DEBUGFS) += ptdump.o
diff --git a/arch/riscv/kvm/ptdump.c b/arch/riscv/kvm/ptdump.c
new file mode 100644
index 000000000..972d45d69
--- /dev/null
+++ b/arch/riscv/kvm/ptdump.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Debug helper used to dump the gstage pagetables of the system.
+ */
+#include <linux/debugfs.h>
+#include <linux/kvm_host.h>
+#include <linux/seq_file.h>
+#include <linux/mm.h>
+#include <asm/ptdump.h>
+#include <asm/pgtable.h>
+
+static const struct ptdump_prot_bits gstage_pte_bits[] = {
+ {
+ .mask = _PAGE_SOFT,
+ .set = "RSW(%d)",
+ .clear = " .. ",
+ }, {
+ .mask = _PAGE_DIRTY,
+ .set = "D",
+ .clear = ".",
+ }, {
+ .mask = _PAGE_ACCESSED,
+ .set = "A",
+ .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[] = {
+ { .name = "PGD" },
+ { .name = "P4D" },
+ { .name = "PUD" },
+ { .name = "PMD" },
+ { .name = "PTE" },
+};
+
+struct kvm_ptdump_state {
+ struct kvm *kvm;
+ struct ptdump_pg_state parser_state;
+ struct addr_marker marker[2];
+ struct ptdump_range range[2];
+};
+
+static void kvm_ptdump_walk_level(struct ptdump_state *pt_st,
+ unsigned long *tbl, int level,
+ unsigned long start_addr)
+{
+ unsigned long addr = start_addr;
+ unsigned long next, virt_addr;
+ int i;
+ unsigned long step = 1UL << (PAGE_SHIFT + (4 - level) * 9);
+
+ for (i = 0; i < PTRS_PER_PTE; i++, addr += step) {
+ unsigned long val = tbl[i];
+
+ next = addr + step;
+
+ if (level == 4 || (val & _PAGE_LEAF) || !(val & _PAGE_PRESENT)) {
+ note_page(pt_st, addr, level, val);
+ } else {
+ unsigned long pa = (val >> _PAGE_PFN_SHIFT) << PAGE_SHIFT;
+
+ virt_addr = (unsigned long)phys_to_virt(pa);
+
+ kvm_ptdump_walk_level(pt_st, (unsigned long *)virt_addr,
+ level + 1, addr);
+ }
+ }
+}
+
+static int kvm_ptdump_visitor(struct seq_file *m, void *v)
+{
+ struct kvm_ptdump_state *st = m->private;
+ struct kvm *kvm = st->kvm;
+ unsigned long *pgd = (unsigned long *)kvm->arch.pgd;
+ int start_level = 5 - kvm->arch.pgd_levels;
+ int i, j;
+
+ st->parser_state.level = -1;
+ st->parser_state.start_address = 0;
+ st->parser_state.seq = m;
+
+ for (i = 0; i < ARRAY_SIZE(gstage_pg_levels); i++) {
+ gstage_pg_levels[i].bits = gstage_pte_bits;
+ gstage_pg_levels[i].num = ARRAY_SIZE(gstage_pte_bits);
+ 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;
+ }
+
+ read_lock(&kvm->mmu_lock);
+ if (pgd) {
+ kvm_ptdump_walk_level(&st->parser_state.ptdump, pgd,
+ start_level, 0);
+ }
+ read_unlock(&kvm->mmu_lock);
+
+ note_page(&st->parser_state.ptdump, 0, -1, 0);
+ return 0;
+}
+
+static int kvm_ptdump_open(struct inode *inode, struct file *file)
+{
+ struct kvm *kvm = inode->i_private;
+ struct kvm_ptdump_state *st;
+ int ret;
+
+ if (!kvm_get_kvm_safe(kvm))
+ return -ENOENT;
+
+ st = kzalloc(sizeof(*st), GFP_KERNEL);
+ if (!st) {
+ kvm_put_kvm(kvm);
+ return -ENOMEM;
+ }
+
+ st->kvm = kvm;
+ st->marker[0].name = "Guest IPA";
+ st->marker[0].start_address = 0;
+ st->marker[1].start_address = -1UL;
+ st->range[0].start = 0;
+ st->range[0].end = -1UL;
+
+ st->parser_state.marker = st->marker;
+ st->parser_state.pg_level = gstage_pg_levels;
+ st->parser_state.ptdump.range = st->range;
+
+ ret = single_open(file, kvm_ptdump_visitor, st);
+ if (ret) {
+ kfree(st);
+ kvm_put_kvm(kvm);
+ }
+ return ret;
+}
+
+static int kvm_ptdump_release(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq = file->private_data;
+ struct kvm_ptdump_state *st = seq->private;
+ struct kvm *kvm = st->kvm;
+
+ kfree(st);
+ kvm_put_kvm(kvm);
+ return single_release(inode, file);
+}
+
+static const struct file_operations kvm_gstage_fops = {
+ .owner = THIS_MODULE,
+ .open = kvm_ptdump_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = kvm_ptdump_release,
+};
+
+void kvm_s2_ptdump_create_debugfs(struct kvm *kvm)
+{
+ debugfs_create_file("gstage_page_tables", 0400, kvm->debugfs_dentry, kvm,
+ &kvm_gstage_fops);
+}
diff --git a/arch/riscv/kvm/vm.c b/arch/riscv/kvm/vm.c
index a9f083fee..464ad2eaf 100644
--- a/arch/riscv/kvm/vm.c
+++ b/arch/riscv/kvm/vm.c
@@ -269,3 +269,8 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
{
return -EINVAL;
}
+
+void kvm_arch_create_vm_debugfs(struct kvm *kvm)
+{
+ kvm_s2_ptdump_create_debugfs(kvm);
+}
--
2.34.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH 1/2] riscv: ptdump: Move pagetable definitions to common header
2026-07-01 8:50 ` [PATCH 1/2] riscv: ptdump: Move pagetable definitions to common header Dylan.Wu
@ 2026-07-01 9:01 ` sashiko-bot
2026-07-21 2:58 ` Paul Walmsley
1 sibling, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-01 9:01 UTC (permalink / raw)
To: Dylan.Wu; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Uninitialized `bits` and `num` fields in `kernel_pg_levels` completely suppress page table attribute output in debugfs.
- [Low] Folded PMD levels on 32-bit RISC-V (RV32) will incorrectly display as 'PMD' instead of 'PGD'.
--
commit f96cd5eefaf170c680412811c0fe9c407b08f9a0
Author: Dylan.Wu <fredwudi0305@gmail.com>
riscv: ptdump: Move pagetable definitions to common header
Move the pagetable walking state and level definitions to a new
arch/riscv/include/asm/ptdump.h header to allow reuse of the attribute parsing
logic. The attribute parsing logic in dump_prot() is updated to use attribute
bits defined within the pg_level structure rather than a global array.
> diff --git a/arch/riscv/mm/ptdump.c b/arch/riscv/mm/ptdump.c
> index f4b4a9fcbbd88..7e55656cbbc92 100644
> --- a/arch/riscv/mm/ptdump.c
> +++ b/arch/riscv/mm/ptdump.c
[ ... ]
> @@ -183,52 +152,48 @@ static const struct prot_bits pte_bits[] = {
> }
> };
>
> -/* Page Level */
> -struct pg_level {
> - const char *name;
> - u64 mask;
> -};
> -
> -static struct pg_level pg_level[] = {
> +static struct ptdump_pg_level kernel_pg_levels[] = {
> { /* pgd */
> .name = "PGD",
> }, { /* p4d */
> - .name = (CONFIG_PGTABLE_LEVELS > 4) ? "P4D" : "PGD",
> + .name = "P4D",
> }, { /* pud */
> - .name = (CONFIG_PGTABLE_LEVELS > 3) ? "PUD" : "PGD",
> + .name = "PUD",
> }, { /* pmd */
> - .name = (CONFIG_PGTABLE_LEVELS > 2) ? "PMD" : "PGD",
> + .name = "PMD",
[Severity: Low]
For 32-bit RISC-V (RV32) architectures that use a 2-level page table where PMD
is folded into PGD, will this cause the top-level entries to incorrectly
display as PMD instead of PGD?
The original code dynamically renamed the PMD level to PGD based on
CONFIG_PGTABLE_LEVELS, but this appears to be omitted from the new code.
> }, { /* pte */
> .name = "PTE",
> },
> };
>
> -static void dump_prot(struct pg_state *st)
> +static void dump_prot(struct ptdump_pg_state *st)
> {
> unsigned int i;
> + const struct ptdump_pg_level *lvl = &st->pg_level[st->level];
> + const struct ptdump_prot_bits *bits = lvl->bits;
>
> - for (i = 0; i < ARRAY_SIZE(pte_bits); i++) {
> + for (i = 0; i < lvl->num; i++) {
[Severity: Medium]
Will this loop ever execute?
The kernel_pg_levels array is statically initialized without setting the .bits
or .num fields. Since .num is implicitly 0, it looks like this loop is entirely
skipped, which will silently discard printing page table protection attributes
to debugfs.
[ ... ]
> @@ -446,12 +414,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;
Does this dynamic initialization loop need to populate the .bits and .num fields
for kernel_pg_levels as well?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701085030.124579-1-fredwudi0305@gmail.com?part=1
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 2/2] KVM: riscv: Register ptdump with debugfs on guest creation
2026-07-01 8:50 ` [PATCH 2/2] " Dylan.Wu
@ 2026-07-01 9:06 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-01 9:06 UTC (permalink / raw)
To: Dylan.Wu; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Lockless reads of the guest PGD pointer (`kvm->arch.pgd`) outside `kvm->mmu_lock` lead to a Use-After-Free (UAF) and potential kernel stack overflow via undefined behavior.
- [High] Unbounded full page table walk under `mmu_lock` combined with `single_open` buffer restarts causes severe lock contention.
- [Medium] Concurrent reads of the debugfs file cause a data race by modifying the global static array `gstage_pg_levels` without synchronization.
- [Medium] The page table walk hardcodes the loop bound to `PTRS_PER_PTE`, silently truncating the guest address space dump for concatenated root tables.
--
commit efa8af5af3c60372dfe6205bc307cb96b4b1cf7a
Author: Dylan.Wu <fredwudi0305@gmail.com>
Subject: KVM: riscv: Register ptdump with debugfs on guest creation
Introduce KVM ptdump to show the guest gstage pagetables. This registers
a 'gstage_page_tables' file under the guest debugfs directory.
Userspace can now inspect the gstage layout and permissions, which
is useful for architectural debugging and memory management audits.
> diff --git a/arch/riscv/kvm/ptdump.c b/arch/riscv/kvm/ptdump.c
> new file mode 100644
> index 0000000000000..972d45d691b2b
> --- /dev/null
> +++ b/arch/riscv/kvm/ptdump.c
[ ... ]
> +static void kvm_ptdump_walk_level(struct ptdump_state *pt_st,
> + unsigned long *tbl, int level,
> + unsigned long start_addr)
> +{
> + unsigned long addr = start_addr;
> + unsigned long next, virt_addr;
> + int i;
> + unsigned long step = 1UL << (PAGE_SHIFT + (4 - level) * 9);
[Severity: High]
Can step calculation result in a negative bitwise shift? If level > 4 (which
could occur if pgd_levels is read as 0 due to concurrent VM destruction), this
triggers undefined behavior.
> +
> + for (i = 0; i < PTRS_PER_PTE; i++, addr += step) {
[Severity: Medium]
Does this hardcoded loop bound truncate the guest address space dump? RISC-V
KVM uses concatenated page tables for the top-level gstage directory (up to
2048 entries for a 16KB root table), but this caps iteration at PTRS_PER_PTE.
> + unsigned long val = tbl[i];
[ ... ]
> +static int kvm_ptdump_visitor(struct seq_file *m, void *v)
> +{
> + struct kvm_ptdump_state *st = m->private;
> + struct kvm *kvm = st->kvm;
> + unsigned long *pgd = (unsigned long *)kvm->arch.pgd;
> + int start_level = 5 - kvm->arch.pgd_levels;
[Severity: High]
Are these lockless reads of pgd and pgd_levels safe? If the VM is torn down
concurrently, kvm_riscv_mmu_free_pgd() could clear these fields and free the
pages. This read happens outside of mmu_lock, which might result in a dangling
pointer being passed to the page table walker.
> + int i, j;
> +
> + st->parser_state.level = -1;
> + st->parser_state.start_address = 0;
> + st->parser_state.seq = m;
> +
> + for (i = 0; i < ARRAY_SIZE(gstage_pg_levels); i++) {
> + gstage_pg_levels[i].bits = gstage_pte_bits;
[Severity: Medium]
Does this loop modify the global static array gstage_pg_levels without any
synchronization? If multiple threads read the debugfs file concurrently, this
data race could cause corrupted or incomplete page table dumps.
> + gstage_pg_levels[i].num = ARRAY_SIZE(gstage_pte_bits);
> + 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;
> + }
> +
> + read_lock(&kvm->mmu_lock);
> + if (pgd) {
> + kvm_ptdump_walk_level(&st->parser_state.ptdump, pgd,
> + start_level, 0);
> + }
> + read_unlock(&kvm->mmu_lock);
[Severity: High]
Could walking the entire page table under mmu_lock cause severe lock contention
or a soft lockup for the guest VM, especially when combined with the buffer
restarts from single_open() below?
> +
> + note_page(&st->parser_state.ptdump, 0, -1, 0);
> + return 0;
> +}
> +
> +static int kvm_ptdump_open(struct inode *inode, struct file *file)
> +{
> + struct kvm *kvm = inode->i_private;
> + struct kvm_ptdump_state *st;
> + int ret;
[ ... ]
> + st->parser_state.marker = st->marker;
> + st->parser_state.pg_level = gstage_pg_levels;
> + st->parser_state.ptdump.range = st->range;
> +
> + ret = single_open(file, kvm_ptdump_visitor, st);
[Severity: High]
Does using single_open() here risk excessive retries? Since single_open()
collects output into a contiguous buffer, if the output exceeds the initial
buffer size, it discards it, doubles the allocation, and restarts the visitor.
Each restart would repeat the entire recursive page table walk under the
mmu_lock.
> + if (ret) {
> + kfree(st);
> + kvm_put_kvm(kvm);
> + }
> + return ret;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701085030.124579-1-fredwudi0305@gmail.com?part=2
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/2] riscv: ptdump: Move pagetable definitions to common header
2026-07-01 8:50 ` [PATCH 1/2] riscv: ptdump: Move pagetable definitions to common header Dylan.Wu
2026-07-01 9:01 ` sashiko-bot
@ 2026-07-21 2:58 ` Paul Walmsley
2026-07-27 12:39 ` Dylan.Wu
2026-07-28 16:14 ` [PATCH v3 0/3] RISC-V KVM gstage page table dumper Dylan.Wu
1 sibling, 2 replies; 14+ messages in thread
From: Paul Walmsley @ 2026-07-21 2:58 UTC (permalink / raw)
To: Dylan.Wu
Cc: palmer, pjw, aou, anup, alex, atish.patra, zhouquan, linux-riscv,
kvm, kvm-riscv, linux-kernel
Hi Dylan,
On Wed, 1 Jul 2026, Dylan.Wu wrote:
> Move the pagetable walking state and level definitions to a new
> arch/riscv/include/asm/ptdump.h header. This allows other parts
> of the kernel (like KVM) to reuse the ptdump attribute parsing
> logic.
>
> To support different pagetable types (like G-stage), the attribute
> parsing logic in dump_prot() is updated to use attribute bits
> defined within the pg_level structure rather than a global array.
Could you please split this into two patches: the first, to create
ptdump.h and move the declarations there and export note_page(); and the
second, to make the attribute parsing and structure changes? That will
make the changes easier to review.
thanks
- Paul
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/2] riscv: ptdump: Move pagetable definitions to common header
2026-07-21 2:58 ` Paul Walmsley
@ 2026-07-27 12:39 ` Dylan.Wu
2026-07-28 16:14 ` [PATCH v3 0/3] RISC-V KVM gstage page table dumper Dylan.Wu
1 sibling, 0 replies; 14+ messages in thread
From: Dylan.Wu @ 2026-07-27 12:39 UTC (permalink / raw)
To: pjw
Cc: palmer, aou, anup, alex, atish.patra, zhouquan, linux-riscv,
kvm-riscv, linux-kernel, kvm
Hi Paul,
Thank you very much for taking the time to review this patch and for
the helpful suggestion.
I have split the patch as you requested and sent v2:
https://lore.kernel.org/linux-riscv/20260727122745.118764-1-fredwudi0305@gmail.com/
Changes in v2:
- Patch 1/3: Create ptdump.h, move declarations there, and export note_page()
- Patch 2/3: Make attribute parsing use per-level bits, rename
pg_level[] to kernel_pg_levels[]
- Patch 3/3: KVM gstage ptdump
I would greatly appreciate any further feedback you may have.
Best regards,
Dylan
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 0/3] RISC-V KVM gstage page table dumper
2026-07-21 2:58 ` Paul Walmsley
2026-07-27 12:39 ` Dylan.Wu
@ 2026-07-28 16:14 ` Dylan.Wu
2026-07-28 16:14 ` [PATCH v3 1/3] riscv: ptdump: Create ptdump.h and move declarations Dylan.Wu
` (2 more replies)
1 sibling, 3 replies; 14+ 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
This series adds support for dumping RISC-V KVM gstage (second-stage)
page tables via debugfs, allowing inspection of the guest physical
address translation setup.
Changes since v2:
- Split the first patch into two as requested by Paul Walmsley:
Patch 1 now only creates ptdump.h and moves declarations there
with no functional changes (structures moved verbatim).
Patch 2 handles the structure renames and per-level attribute
bit parsing logic.
Patch 1: Create ptdump.h and move declarations
Patch 2: Use per-level attribute bits for parsing
Patch 3: Register gstage ptdump with debugfs on guest creation
Dylan.Wu (3):
riscv: ptdump: Create ptdump.h and move declarations
riscv: ptdump: Use per-level attribute bits for parsing
KVM: riscv: Register ptdump with debugfs on guest creation
arch/riscv/include/asm/ptdump.h | 43 ++++++++
arch/riscv/kvm/Kconfig | 12 +++
arch/riscv/kvm/Makefile | 1 +
arch/riscv/kvm/ptdump.c | 177 ++++++++++++++++++++++++++++++++
arch/riscv/mm/ptdump.c | 106 ++++++++-----------
5 files changed, 275 insertions(+), 64 deletions(-)
create mode 100644 arch/riscv/include/asm/ptdump.h
create mode 100644 arch/riscv/kvm/ptdump.c
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread
* [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:26 ` sashiko-bot
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, 1 reply; 14+ 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 related [flat|nested] 14+ 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:31 ` sashiko-bot
2026-07-28 16:14 ` [PATCH v3 3/3] KVM: riscv: Register ptdump with debugfs on guest creation Dylan.Wu
2 siblings, 1 reply; 14+ 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 related [flat|nested] 14+ 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-07-28 16:50 ` sashiko-bot
2 siblings, 1 reply; 14+ 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 related [flat|nested] 14+ messages in thread
* Re: [PATCH v3 1/3] riscv: ptdump: Create ptdump.h and move declarations
2026-07-28 16:14 ` [PATCH v3 1/3] riscv: ptdump: Create ptdump.h and move declarations Dylan.Wu
@ 2026-07-28 16:26 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-28 16:26 UTC (permalink / raw)
To: Dylan.Wu; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Exporting the generic symbol `note_page` pollutes the kernel's global namespace.
--
--- Patch [1]: [PATCH v3 1/3] riscv: ptdump: Create ptdump.h and move declarations ---
commit f9b97fdc17623863fd64f27ed80770e08d139f2a
Author: Dylan.Wu <fredwudi0305@gmail.com>
riscv: ptdump: Create ptdump.h and move declarations
This commit extracts page table dumping structures and level definitions
into a new header file, arch/riscv/include/asm/ptdump.h. It also exports
the note_page function for use by other kernel components like KVM.
> diff --git a/arch/riscv/mm/ptdump.c b/arch/riscv/mm/ptdump.c
> index f4b4a9fcbbd88..d9a955b8a532d 100644
> --- a/arch/riscv/mm/ptdump.c
> +++ b/arch/riscv/mm/ptdump.c
[ ... ]
> @@ -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);
[Severity: Low]
Does exporting this highly generic name (note_page) without an architectural
or subsystem prefix risk build or link collisions?
Since it is now in the global module namespace, should it be renamed with a
prefix like ptdump_ or riscv_ to avoid polluting the global namespace and
adhere to standard kernel naming conventions?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260728161449.190058-1-fredwudi0305@gmail.com?part=1
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 2/3] riscv: ptdump: Use per-level attribute bits for parsing
2026-07-28 16:14 ` [PATCH v3 2/3] riscv: ptdump: Use per-level attribute bits for parsing Dylan.Wu
@ 2026-07-28 16:31 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-28 16:31 UTC (permalink / raw)
To: Dylan.Wu; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Incomplete transition to per-instance and per-level page table attributes in ptdump. The commit introduces a `pg_level` pointer in `struct ptdump_pg_state` to allow different callers (like future KVM gstage code) to use their own attribute arrays. However, while `dump_prot()` uses this pointer, `note_page()` and `dump_addr()` still hardcode the global `kernel_pg_levels` array. Additionally, `ptdump_init()` hardcodes `pte_bits` when calculating masks instead of using the newly added per-level `bits` and `num` fields.
--
--- Patch [2]: [PATCH v3 2/3] riscv: ptdump: Use per-level attribute bits for parsing ---
commit 306ba7e65dcddd613da201b658930774a642dd75
Author: Dylan.Wu <fredwudi0305@gmail.com>
riscv: ptdump: Use per-level attribute bits for parsing
This commit renames ptdump structures and adds a pg_level pointer to
ptdump_pg_state. This allows different callers (like future KVM gstage code)
to define and use their own page table attributes rather than relying on a
global array.
> diff --git a/arch/riscv/mm/ptdump.c b/arch/riscv/mm/ptdump.c
> index d9a955b8a532d..c67fbbf5ff624 100644
> --- a/arch/riscv/mm/ptdump.c
> +++ b/arch/riscv/mm/ptdump.c
[ ... ]
> @@ -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);
[Severity: Medium]
Will this print incorrect level names if a caller provides a custom
pg_level array? If the goal is to allow per-instance attribute arrays,
should this use st->pg_level[st->level].name instead of the global
kernel_pg_levels array?
[ ... ]
> @@ -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;
[Severity: Medium]
Does this bypass the custom attributes provided by the caller?
Using the hardcoded kernel_pg_levels array here will mask the PTE values
with the global mask, rather than using st->pg_level[level].mask from the
ptdump_pg_state instance.
[ ... ]
> @@ -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;
[Severity: Medium]
Since kernel_pg_levels[i] now contains its own .bits and .num fields,
should this initialization loop iterate over kernel_pg_levels[i].bits
up to kernel_pg_levels[i].num instead of hardcoding the global pte_bits
array?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260728161449.190058-1-fredwudi0305@gmail.com?part=2
^ permalink raw reply [flat|nested] 14+ 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-07-28 16:50 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-28 16:50 UTC (permalink / raw)
To: Dylan.Wu; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] Lockless traversal of `kvm->arch.pgd` using host `mmap_write_lock` instead of `kvm->mmu_lock` leads to a Use-After-Free if gstage page tables are concurrently freed.
- [High] The generic host page table walker is structurally incompatible with KVM guest stage-2 page tables, leading to bogus walks and kernel panics.
- [Medium] Passing a NULL `kvm->arch.pgd` to the core page table walker causes it to silently fall back to walking the host process's page tables.
- [Medium] Mutating the static global array `gstage_pg_levels` without synchronization causes data races and permanently corrupts the output layout across different VMs.
- [Medium] Incompatible reuse of the host's `note_page()` function breaks range coalescing and prints incorrect output names.
- [Low] Linker error when KVM is compiled as a loadable module due to missing export of `ptdump_walk_pgd`.
--
--- Patch [3]: [PATCH v3 3/3] KVM: riscv: Register ptdump with debugfs on guest creation ---
commit 5cf74731d352654675c1f47276baa2185271e44d
Author: Dylan.Wu <fredwudi0305@gmail.com>
KVM: riscv: Register ptdump with debugfs on guest creation
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.
> diff --git a/arch/riscv/kvm/ptdump.c b/arch/riscv/kvm/ptdump.c
> new file mode 100644
> --- /dev/null
> +++ b/arch/riscv/kvm/ptdump.c
[ ... ]
> +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;
> +}
[Severity: Medium]
Will reusing the host note_page() function break range coalescing and
produce incorrect output?
The host note_page() function hardcodes the use of kernel_pg_levels to
compute the page protection mask. Because kernel_pg_levels and
gstage_pg_levels mask different bits, won't note_page() falsely detect a
protection change for almost every page before we override it here?
Additionally, it seems note_page() will use kernel_pg_levels to print out
the directory names, potentially printing incorrect level names for the
guest entries.
[ ... ]
> +static int gstage_ptdump_show(struct seq_file *m, void *v)
> +{
> + struct kvm *kvm = m->private;
[ ... ]
> + 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";
[Severity: Medium]
Does mutating the static global gstage_pg_levels array here cause data
races?
Since debugfs files can be read concurrently for multiple VMs with different
pgd_levels, won't these lockless writes corrupt the array for subsequent
reads of other VMs?
> +
> + ptdump_walk_pgd(&st.ptdump, kvm->mm, kvm->arch.pgd);
[Severity: Critical]
Does this lockless traversal risk a use-after-free of the gstage page tables?
KVM guest stage-2 page tables are strictly managed under kvm->mmu_lock. The
debugfs walker acquires the host process's mmap_write_lock via
ptdump_walk_pgd(), but does not hold kvm->mmu_lock.
If a concurrent KVM operation such as a memslot removal or an MMU notifier
fires and calls kvm_riscv_gstage_unmap_range(), it will free the intermediate
page table pages. Will this walker concurrently read freed memory?
[Severity: High]
Is the generic host page table walker structurally compatible with KVM guest
stage-2 page tables?
The generic walker relies on compile-time host macros like PGDIR_SHIFT and
PTRS_PER_PGD. However, RISC-V KVM gstage page tables have dynamic geometries
based on kvm->arch.pgd_levels.
When the host macros are applied to the gstage root, will the walker
compute incorrect indices and treat guest leaf PTEs as intermediate
directories, causing out-of-bounds memory accesses?
[Severity: Medium]
What happens if the debugfs file is read before the gstage root is
allocated?
At VM initialization, kvm->arch.pgd is NULL. If a NULL pgd is passed to
walk_pgd_range(), won't it silently fall back to walking the host process's
page tables via pgd_offset(walk->mm, addr)?
[Severity: Low]
Will this cause a build failure when KVM is compiled as a loadable module?
The core kernel symbol ptdump_walk_pgd() is not exported to modules via
EXPORT_SYMBOL(), which might cause a linker error here when KVM is built as
a module.
> +
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260728161449.190058-1-fredwudi0305@gmail.com?part=3
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-07-28 16:50 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-01 8:50 [PATCH 0/2] riscv: ptdump: Refactor for KVM gstage ptdump support Dylan.Wu
2026-07-01 8:50 ` [PATCH 1/2] riscv: ptdump: Move pagetable definitions to common header Dylan.Wu
2026-07-01 9:01 ` sashiko-bot
2026-07-21 2:58 ` Paul Walmsley
2026-07-27 12:39 ` Dylan.Wu
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:26 ` sashiko-bot
2026-07-28 16:14 ` [PATCH v3 2/3] riscv: ptdump: Use per-level attribute bits for parsing Dylan.Wu
2026-07-28 16:31 ` sashiko-bot
2026-07-28 16:14 ` [PATCH v3 3/3] KVM: riscv: Register ptdump with debugfs on guest creation Dylan.Wu
2026-07-28 16:50 ` sashiko-bot
2026-07-01 8:50 ` [PATCH 2/2] " Dylan.Wu
2026-07-01 9:06 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox