[PATCH v6 22/90] x86/cpuid: Introduce a parser debugfs interface

From: Ahmed S. Darwish

Date: Thu Mar 26 2026 - 22:24:35 EST


Introduce the debugfs files "x86/cpuid/[0-ncpus]" to dump each CPU's cached
CPUID table. For each cached leaf/subleaf, invoke the CPUID instruction on
the target CPU and compare the hardware result against its cached values.

Mark any mismatched cached CPUID output value with an asterisk. This
should help with tricky bug reports in the future if the cached CPUID data
get unexpectedly out of sync with actual hardware state.

Note, expose cpuid_phases[] via "cpuid_parser.h" to allow the debugfs code
to traverse and dump parsed CPUID data.

Note, this debugfs interface also simplifies the development and testing of
adding new leaves to the CPUID parser.

Signed-off-by: Ahmed S. Darwish <darwi@xxxxxxxxxxxxx>
---
arch/x86/kernel/cpu/Makefile | 2 +-
arch/x86/kernel/cpu/cpuid_debugfs.c | 108 ++++++++++++++++++++++++++++
arch/x86/kernel/cpu/cpuid_parser.c | 9 ++-
arch/x86/kernel/cpu/cpuid_parser.h | 12 ++++
4 files changed, 125 insertions(+), 6 deletions(-)
create mode 100644 arch/x86/kernel/cpu/cpuid_debugfs.c

diff --git a/arch/x86/kernel/cpu/Makefile b/arch/x86/kernel/cpu/Makefile
index d2e8a849f180..d62e2d60a965 100644
--- a/arch/x86/kernel/cpu/Makefile
+++ b/arch/x86/kernel/cpu/Makefile
@@ -62,7 +62,7 @@ obj-$(CONFIG_HYPERVISOR_GUEST) += vmware.o hypervisor.o mshyperv.o
obj-$(CONFIG_BHYVE_GUEST) += bhyve.o
obj-$(CONFIG_ACRN_GUEST) += acrn.o

-obj-$(CONFIG_DEBUG_FS) += debugfs.o
+obj-$(CONFIG_DEBUG_FS) += debugfs.o cpuid_debugfs.o

obj-$(CONFIG_X86_BUS_LOCK_DETECT) += bus_lock.o

diff --git a/arch/x86/kernel/cpu/cpuid_debugfs.c b/arch/x86/kernel/cpu/cpuid_debugfs.c
new file mode 100644
index 000000000000..4bd874bffffc
--- /dev/null
+++ b/arch/x86/kernel/cpu/cpuid_debugfs.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CPUID parser debugfs entries: x86/cpuid/[0-ncpus]
+ *
+ * Dump each CPU's cached CPUID table and compare its values against current
+ * CPUID output on that CPU. Mark changed entries with an asterisk.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/init.h>
+#include <linux/smp.h>
+#include <linux/types.h>
+
+#include <asm/cpuid/api.h>
+#include <asm/percpu.h>
+#include <asm/processor.h>
+
+#include "cpuid_parser.h"
+
+static void cpuid_this_cpu(void *info)
+{
+ struct cpuid_regs *regs = info;
+
+ __cpuid(&regs->eax, &regs->ebx, &regs->ecx, &regs->edx);
+}
+
+static void
+cpuid_show_leaf(struct seq_file *m, uintptr_t cpu_id, const struct cpuid_parse_entry *entry,
+ const struct leaf_parse_info *info, const struct cpuid_regs *cached)
+{
+ for (int j = 0; j < info->nr_entries; j++) {
+ u32 subleaf = entry->subleaf + j;
+ struct cpuid_regs regs = {
+ .eax = entry->leaf,
+ .ecx = subleaf,
+ };
+ int ret;
+
+ seq_printf(m, "Leaf 0x%08x, subleaf %u:\n", entry->leaf, subleaf);
+
+ ret = smp_call_function_single(cpu_id, cpuid_this_cpu, &regs, true);
+ if (ret) {
+ seq_printf(m, "Failed to invoke CPUID on CPU %lu: %d\n\n", cpu_id, ret);
+ continue;
+ }
+
+ seq_printf(m, " cached: %cEAX=0x%08x %cEBX=0x%08x %cECX=0x%08x %cEDX=0x%08x\n",
+ cached[j].eax == regs.eax ? ' ' : '*', cached[j].eax,
+ cached[j].ebx == regs.ebx ? ' ' : '*', cached[j].ebx,
+ cached[j].ecx == regs.ecx ? ' ' : '*', cached[j].ecx,
+ cached[j].edx == regs.edx ? ' ' : '*', cached[j].edx);
+ seq_printf(m, " actual: EAX=0x%08x EBX=0x%08x ECX=0x%08x EDX=0x%08x\n",
+ regs.eax, regs.ebx, regs.ecx, regs.edx);
+ }
+}
+
+static void __cpuid_debug_show(struct seq_file *m, uintptr_t cpu_id,
+ const struct cpuid_parse_entry *entry, int nr_entries)
+{
+ const struct cpuinfo_x86 *c = per_cpu_ptr(&cpu_info, cpu_id);
+ const struct cpuid_table *t = &c->cpuid;
+
+ for (int i = 0; i < nr_entries; i++, entry++) {
+ const struct leaf_parse_info *qi = cpuid_table_info_p(t, entry->info_offs);
+ const struct cpuid_regs *qr = cpuid_table_regs_p(t, entry->regs_offs);
+
+ cpuid_show_leaf(m, cpu_id, entry, qi, qr);
+ }
+}
+
+static int cpuid_debug_show(struct seq_file *m, void *p)
+{
+ uintptr_t cpu_id = (uintptr_t)m->private;
+
+ for (int i = 0; i < cpuid_nphases; i++)
+ __cpuid_debug_show(m, cpu_id, cpuid_phases[i].table, cpuid_phases[i].nr_entries);
+
+ return 0;
+}
+
+static int cpuid_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, cpuid_debug_show, inode->i_private);
+}
+
+static const struct file_operations cpuid_ops = {
+ .open = cpuid_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static __init int cpuid_init_debugfs(void)
+{
+ struct dentry *dir;
+ uintptr_t cpu_id;
+ char cpu_name[24];
+
+ dir = debugfs_create_dir("cpuid", arch_debugfs_dir);
+
+ for_each_possible_cpu(cpu_id) {
+ scnprintf(cpu_name, sizeof(cpu_name), "%lu", cpu_id);
+ debugfs_create_file(cpu_name, 0444, dir, (void *)cpu_id, &cpuid_ops);
+ }
+
+ return 0;
+}
+late_initcall(cpuid_init_debugfs);
diff --git a/arch/x86/kernel/cpu/cpuid_parser.c b/arch/x86/kernel/cpu/cpuid_parser.c
index 97b7f296df03..ab736f03051e 100644
--- a/arch/x86/kernel/cpu/cpuid_parser.c
+++ b/arch/x86/kernel/cpu/cpuid_parser.c
@@ -74,14 +74,13 @@ static const struct cpuid_parse_entry cpuid_common_entries[] = {
CPUID_COMMON_ENTRIES
};

-static const struct {
- const struct cpuid_parse_entry *table;
- int nr_entries;
-} cpuid_phases[] = {
+const struct cpuid_phase cpuid_phases[] = {
{ cpuid_early_entries, ARRAY_SIZE(cpuid_early_entries) },
{ cpuid_common_entries, ARRAY_SIZE(cpuid_common_entries) },
};

+const int cpuid_nphases = ARRAY_SIZE(cpuid_phases);
+
/*
* Leaf-independent parser code:
*/
@@ -189,7 +188,7 @@ cpuid_fill_table(struct cpuid_table *t, const struct cpuid_parse_entry entries[]

static void __cpuid_scan_cpu_full(struct cpuinfo_x86 *c, bool early_boot)
{
- int nphases = early_boot ? 1 : ARRAY_SIZE(cpuid_phases);
+ int nphases = early_boot ? 1 : cpuid_nphases;
struct cpuid_table *table = &c->cpuid;

for (int i = 0; i < nphases; i++)
diff --git a/arch/x86/kernel/cpu/cpuid_parser.h b/arch/x86/kernel/cpu/cpuid_parser.h
index a3f7dcc6c03f..8b0d44b745c5 100644
--- a/arch/x86/kernel/cpu/cpuid_parser.h
+++ b/arch/x86/kernel/cpu/cpuid_parser.h
@@ -149,6 +149,18 @@ struct cpuid_parse_entry {
CPUID_PARSE_ENTRY ( 0x80000003, 0, generic ), \
CPUID_PARSE_ENTRY ( 0x80000004, 0, generic ), \

+/*
+ * CPUID parser phases:
+ */
+
+struct cpuid_phase {
+ const struct cpuid_parse_entry *table;
+ int nr_entries;
+};
+
+extern const struct cpuid_phase cpuid_phases[];
+extern const int cpuid_nphases;
+
/*
* CPUID leaf vendor table:
*/
--
2.53.0