[PATCH v3 6/6] x86/hygon: Add SMN debugfs interface

From: Lin Wang

Date: Thu Jul 09 2026 - 03:59:45 EST


Add an optional debugfs interface for issuing raw Hygon SMN reads and
writes. The interface is intended as a kernel-developer debug aid and
is disabled by default.

Enable on the kernel command line:

hygon_smn_debugfs_enable

When enabled and SMN setup succeeds, four files appear under
/sys/kernel/debug/x86/hygon_smn/:

topology - read-only. Prints a node count summary (num_nodes,
num_cdd, num_sockets, cdd_per_socket) and the dense DF
node id -> (socket, dfid, type, PCI BDF) mapping. Unlike
AMD, Hygon DF nodes are not at fixed PCI slots and the
node id space is a dense range partitioned into CDD
nodes first, then IOD nodes. This file exposes the valid
node range and the hardware identity of each dense node
id.
node - DF node index that subsequent value reads/writes target;
writes are validated against hygon_node_num().
address - SMN address to read or write.
value - read returns the 32-bit value at (node, address);
write performs an SMN write and taints the kernel with
TAINT_CPU_OUT_OF_SPEC, because arbitrary SMN writes can
leave the CPU in an undefined state.

The interface is created from hygon_smn_setup() after
hygon_smn_exclusive is set, so the underlying hygon_smn_read /
hygon_smn_write APIs are available when a debugfs op is
issued. For the same reason cache.ready should already be true when
"topology" is readable; the show handler still guards on it and
returns -ENODEV if it is not, which would be an internal ordering
error.

The node/address/value selector-style debugfs interface follows the
existing amd_smn debugfs model. It is kept as an opt-in developer
debug aid instead of introducing a different Hygon-only debug ABI.

No new in-kernel API. No effect on systems that do not opt in via the
boot parameter.

Signed-off-by: Lin Wang <wanglin@xxxxxxxxxxxxxx>
---
.../admin-guide/kernel-parameters.txt | 8 +
arch/x86/kernel/hygon_node.c | 183 ++++++++++++++++++
2 files changed, 191 insertions(+)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index b5493a7f8f22..06d85930af01 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -2135,6 +2135,14 @@ Kernel parameters
Hardware protection action taken on critical events like
overtemperature or imminent voltage loss.

+ hygon_smn_debugfs_enable
+ [X86] Enable the Hygon Family 0x18 SMN debugfs
+ interface at /sys/kernel/debug/x86/hygon_smn/, which
+ allows raw reads and writes of System Management
+ Network (SMN) registers. Requires CONFIG_HYGON_NODE
+ and CONFIG_DEBUG_FS. Off by default; intended for
+ development and debugging only.
+
i2c_bus= [HW] Override the default board specific I2C bus speed
or register an additional I2C bus that is not
registered from board initialization code.
diff --git a/arch/x86/kernel/hygon_node.c b/arch/x86/kernel/hygon_node.c
index 09c08aa0c2c7..9f597e3f377a 100644
--- a/arch/x86/kernel/hygon_node.c
+++ b/arch/x86/kernel/hygon_node.c
@@ -27,6 +27,7 @@
#include <linux/bitops.h>
#include <linux/cleanup.h>
#include <linux/cpu.h>
+#include <linux/debugfs.h>
#include <linux/export.h>
#include <linux/init.h>
#include <linux/mutex.h>
@@ -870,6 +871,185 @@ static void __init hygon_release_root_regions(struct pci_dev **roots,
pci_release_config_region(roots[i], 0, PCI_CFG_SPACE_SIZE);
}

+/*
+ * Optional Hygon SMN debugfs interface.
+ *
+ * Disabled by default; enable with the "hygon_smn_debugfs_enable" kernel
+ * command-line option. When enabled, this exposes four files under
+ * /sys/kernel/debug/x86/hygon_smn/:
+ *
+ * topology (ro) -- node count summary and the dense DF node id ->
+ * (socket, dfid, type, PCI BDF) mapping, so a
+ * developer can discover the valid range for "node"
+ * and what each dense id refers to in hardware.
+ * node (rw) -- select the target dense DF node id.
+ * address (rw) -- select the target SMN address.
+ * value (rw) -- read/write the SMN value at (node, address).
+ *
+ * A write to "value" performs an SMN write and taints the kernel with
+ * TAINT_CPU_OUT_OF_SPEC, because arbitrary SMN writes can put the CPU
+ * into an undefined state.
+ */
+static bool hygon_smn_enable_dfs __initdata;
+
+static int __init hygon_smn_enable_debugfs(char *str)
+{
+ hygon_smn_enable_dfs = true;
+ return 1;
+}
+__setup("hygon_smn_debugfs_enable", hygon_smn_enable_debugfs);
+
+static struct dentry *hygon_smn_debugfs_dir;
+
+/*
+ * The node/address/value selector layout follows the existing amd_smn
+ * debugfs model: global selector state for an opt-in, single-user
+ * developer debug interface.
+ */
+static u16 hygon_smn_debug_node;
+static u32 hygon_smn_debug_address;
+
+/*
+ * "topology" -- read-only summary plus the dense DF node id ->
+ * (socket, dfid, type, PCI BDF) mapping. This is the discovery file
+ * for the SMN interface: it tells the user the valid range for "node"
+ * (0 .. num_nodes - 1), which dense ids are CDD vs IOD, and which PCI
+ * misc (F3) / link (F4) functions back each node.
+ *
+ * The file only exists once hygon_smn_debugfs_init() has run, which is
+ * after hygon_build_cache() set cache.ready, so a !ready state here is
+ * an internal ordering error rather than a normal transient. Report it
+ * as -ENODEV instead of printing misleading values.
+ */
+static int hygon_smn_topology_show(struct seq_file *m, void *v)
+{
+ u16 i;
+
+ if (!hygon_cache.ready)
+ return -ENODEV;
+
+ seq_printf(m, "num_nodes: %u\n", hygon_cache.num_nodes);
+ seq_printf(m, "num_cdd: %u\n", hygon_cache.num_cdd);
+ seq_printf(m, "num_sockets: %u\n", hygon_cache.num_sockets);
+ seq_printf(m, "cdd_per_socket: %u\n", hygon_cache.cdd_per_socket);
+ seq_putc(m, '\n');
+
+ seq_printf(m, "%-4s %-6s %-4s %-4s %-12s %-12s\n",
+ "node", "socket", "dfid", "type", "misc", "link");
+
+ for (i = 0; i < hygon_cache.num_nodes; i++) {
+ const struct hygon_node *node = &hygon_cache.nodes[i];
+
+ seq_printf(m, "%-4u %-6u %-4u %-4s %-12s %-12s\n",
+ i, node->socket_id, node->dfid,
+ node->is_cdd ? "CDD" : "IOD",
+ pci_name(node->misc), pci_name(node->link));
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(hygon_smn_topology);
+
+static ssize_t hygon_smn_node_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ u16 node;
+ int ret;
+
+ ret = kstrtou16_from_user(userbuf, count, 0, &node);
+ if (ret)
+ return ret;
+
+ if (node >= hygon_node_num())
+ return -ENODEV;
+
+ hygon_smn_debug_node = node;
+ return count;
+}
+
+static int hygon_smn_node_show(struct seq_file *m, void *v)
+{
+ seq_printf(m, "0x%08x\n", hygon_smn_debug_node);
+ return 0;
+}
+
+static ssize_t hygon_smn_address_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ int ret;
+
+ ret = kstrtouint_from_user(userbuf, count, 0, &hygon_smn_debug_address);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static int hygon_smn_address_show(struct seq_file *m, void *v)
+{
+ seq_printf(m, "0x%08x\n", hygon_smn_debug_address);
+ return 0;
+}
+
+static int hygon_smn_value_show(struct seq_file *m, void *v)
+{
+ u32 val;
+ int ret;
+
+ ret = hygon_smn_read(hygon_smn_debug_node,
+ hygon_smn_debug_address, &val);
+ if (ret)
+ return ret;
+
+ seq_printf(m, "0x%08x\n", val);
+ return 0;
+}
+
+static ssize_t hygon_smn_value_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ u32 val;
+ int ret;
+
+ ret = kstrtouint_from_user(userbuf, count, 0, &val);
+ if (ret)
+ return ret;
+
+ add_taint(TAINT_CPU_OUT_OF_SPEC, LOCKDEP_STILL_OK);
+
+ ret = hygon_smn_write(hygon_smn_debug_node,
+ hygon_smn_debug_address, val);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+DEFINE_SHOW_STORE_ATTRIBUTE(hygon_smn_node);
+DEFINE_SHOW_STORE_ATTRIBUTE(hygon_smn_address);
+DEFINE_SHOW_STORE_ATTRIBUTE(hygon_smn_value);
+
+static void __init hygon_smn_debugfs_init(void)
+{
+ if (!hygon_smn_enable_dfs)
+ return;
+
+ hygon_smn_debugfs_dir = debugfs_create_dir("hygon_smn",
+ arch_debugfs_dir);
+
+ debugfs_create_file("topology", 0400, hygon_smn_debugfs_dir, NULL,
+ &hygon_smn_topology_fops);
+ debugfs_create_file("node", 0600, hygon_smn_debugfs_dir, NULL,
+ &hygon_smn_node_fops);
+ debugfs_create_file("address", 0600, hygon_smn_debugfs_dir, NULL,
+ &hygon_smn_address_fops);
+ debugfs_create_file("value", 0600, hygon_smn_debugfs_dir, NULL,
+ &hygon_smn_value_fops);
+}
+
/*
* Hygon SMN setup.
*
@@ -1032,6 +1212,9 @@ static int __init hygon_smn_setup(void)
hygon_smn_roots = roots;
hygon_smn_num_nodes = num_nodes;
hygon_smn_exclusive = true;
+
+ hygon_smn_debugfs_init();
+
return 0;

err_release:
--
2.43.0