From: "Dylan.Wu" <fredwudi0305@gmail.com>
To: palmer@dabbelt.com, pjw@kernel.org, aou@eecs.berkeley.edu,
anup@brainfault.org
Cc: alex@ghiti.fr, atish.patra@linux.dev, zhouquan@iscas.ac.cn,
linux-riscv@lists.infradead.org, kvm@vger.kernel.org,
kvm-riscv@lists.infradead.org, linux-kernel@vger.kernel.org,
"Dylan.Wu" <fredwudi0305@gmail.com>
Subject: [PATCH 2/2] KVM: riscv: Register ptdump with debugfs on guest creation
Date: Wed, 1 Jul 2026 04:50:30 -0400 [thread overview]
Message-ID: <20260701085030.124579-3-fredwudi0305@gmail.com> (raw)
In-Reply-To: <20260701085030.124579-1-fredwudi0305@gmail.com>
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
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
WARNING: multiple messages have this Message-ID (diff)
From: "Dylan.Wu" <fredwudi0305@gmail.com>
To: palmer@dabbelt.com, pjw@kernel.org, aou@eecs.berkeley.edu,
anup@brainfault.org
Cc: alex@ghiti.fr, atish.patra@linux.dev, zhouquan@iscas.ac.cn,
linux-riscv@lists.infradead.org, kvm@vger.kernel.org,
kvm-riscv@lists.infradead.org, linux-kernel@vger.kernel.org,
"Dylan.Wu" <fredwudi0305@gmail.com>
Subject: [PATCH 2/2] KVM: riscv: Register ptdump with debugfs on guest creation
Date: Wed, 1 Jul 2026 04:50:30 -0400 [thread overview]
Message-ID: <20260701085030.124579-3-fredwudi0305@gmail.com> (raw)
In-Reply-To: <20260701085030.124579-1-fredwudi0305@gmail.com>
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
WARNING: multiple messages have this Message-ID (diff)
From: "Dylan.Wu" <fredwudi0305@gmail.com>
To: palmer@dabbelt.com, pjw@kernel.org, aou@eecs.berkeley.edu,
anup@brainfault.org
Cc: alex@ghiti.fr, atish.patra@linux.dev, zhouquan@iscas.ac.cn,
linux-riscv@lists.infradead.org, kvm@vger.kernel.org,
kvm-riscv@lists.infradead.org, linux-kernel@vger.kernel.org,
"Dylan.Wu" <fredwudi0305@gmail.com>
Subject: [PATCH 2/2] KVM: riscv: Register ptdump with debugfs on guest creation
Date: Wed, 1 Jul 2026 04:50:30 -0400 [thread overview]
Message-ID: <20260701085030.124579-3-fredwudi0305@gmail.com> (raw)
In-Reply-To: <20260701085030.124579-1-fredwudi0305@gmail.com>
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
--
kvm-riscv mailing list
kvm-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/kvm-riscv
next prev parent reply other threads:[~2026-07-01 8:51 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
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 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 ` Dylan.Wu
2026-07-01 8:50 ` Dylan.Wu
2026-07-01 9:01 ` sashiko-bot
2026-07-01 8:50 ` Dylan.Wu [this message]
2026-07-01 8:50 ` [PATCH 2/2] KVM: riscv: Register ptdump with debugfs on guest creation Dylan.Wu
2026-07-01 8:50 ` Dylan.Wu
2026-07-01 9:06 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260701085030.124579-3-fredwudi0305@gmail.com \
--to=fredwudi0305@gmail.com \
--cc=alex@ghiti.fr \
--cc=anup@brainfault.org \
--cc=aou@eecs.berkeley.edu \
--cc=atish.patra@linux.dev \
--cc=kvm-riscv@lists.infradead.org \
--cc=kvm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-riscv@lists.infradead.org \
--cc=palmer@dabbelt.com \
--cc=pjw@kernel.org \
--cc=zhouquan@iscas.ac.cn \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.