* [RFC PATCH 00/05] x86/hygon: Add Family 0x18 node enumeration and SMN access
@ 2026-04-02 11:15 Lin Wang
2026-04-02 11:15 ` [RFC PATCH 1/5] pci_ids: Add Hygon Family 0x18 DF device IDs Lin Wang
` (4 more replies)
0 siblings, 5 replies; 12+ messages in thread
From: Lin Wang @ 2026-04-02 11:15 UTC (permalink / raw)
To: yazen.ghannam, mario.limonciello, Borislav Petkov
Cc: tglx, mingo, x86, linux-kernel
== Background ==
The "AMD NB and SMN rework" series [1][2] restructured AMD northbridge
and SMN support into amd_nb.c and amd_node.c, providing a clean
framework for discovering and accessing AMD Data Fabric (DF) instances
via PCI config space and SMN. The Hygon vendor ID is already recognized
in amd_node.c (get_next_root()) and early_is_amd_nb(), but the mainline
kernel currently lacks support for Hygon Data Fabric node enumeration.
Hygon Family 0x18 implements a Data Fabric topology that is structurally
comparable to AMD Zen systems at the register level, but diverges from
several platform conventions that amd_nb.c and amd_node.c rely on. This
series adds the missing Hygon DF support by building on the existing AMD
node framework through a set of Hygon-specific hooks, rather than forking
the code paths. The goal is to keep changes to existing AMD code minimal
and self-contained.
== Overview ==
This series adds the missing kernel infrastructure for two closely
related functions on Hygon Family 0x18:
1. DF node enumeration -- discovering and identifying each Data Fabric
(DF) instance on the system through its PCI config registers, so
that the NB and SMN subsystems can access them correctly.
2. CPU-to-node mapping -- establishing a canonical logical node ID that
bridges the DF register world (socket_id, DFID) and the CPU
topology world (phys_node_id from CPUID), so that EDAC, MCE, and
ATL consumers can translate a CPU reference to the correct DF node
in O(1).
== Problem ==
AMD Zen-based systems enumerate DF nodes at fixed PCI slots 00:18.x
through 00:1f.x on segment 0 bus 0. The slot-minus-0x18 identity is a
platform guarantee that amd_nb.c and amd_node.c rely on throughout.
Hygon Family 0x18 processors expose DF instances at platform-assigned
PCI slots with no fixed relationship to node identity. As a result:
- amd_nb.c cannot populate amd_northbridges[] for Hygon systems.
- amd_smn_init() cannot assign SMN root devices to the correct nodes.
- There is no kernel mechanism to map a CPU to its DF node. The
phys_node_id values from CPUID 8000001Eh[7:0] are globally unique
but sparse (socket 0: 0..3, socket 1: 16..19, etc.) and cannot be
used directly as indices. The fundamental challenge, however, is not
merely compacting a sparse range -- it is establishing a reliable
mapping between two independent hardware domains: the DF register
world (socket_id and DFID from PCI config space) and the CPU topology
world (phys_node_id from CPUID). Neither domain references the other
directly. A correct mapping must be grounded in hardware-fixed
properties, not software enumeration order. EDAC, MCE decode, and
address translation all depend on this correspondence.
== Solution ==
hygon_node.c is a Hygon-specific DF enumeration module that reads
hardware identity directly from each DF instance, builds a correctly
ordered node array, and provides the CPU-to-node mapping as an O(1)
lookup. It integrates with the existing AMD subsystems through a narrow
set of hooks rather than forking the code paths.
== Hardware Background ==
Each DF instance exposes identity through PCI config registers on its
function siblings:
F1x200 (SystemCfg), all models:
[30:28] MySocketId -- hardware socket ID
[23:20] MyDieId -- die ID (equals DFID on most models)
F5x180 (FabricId), Model 0x06-0x08 only:
[19:16] DFID -- real Data Fabric ID (MyDieId != DFID here)
DFID classifies each DF instance:
DFID >= 4: Compute Die (CDD) -- hosts CPU cores and UMC controllers.
Each CDD has a platform-unique phys_node_id from
CPUID leaf 8000001Eh[7:0].
DFID < 4: I/O Die (IOD) -- interconnect and I/O; no CPUs, no UMC.
The phys_node_id is globally unique but sparse across sockets:
4-socket, 4 CDD/socket (Model 0x04/0x05):
Socket 0: DFID=4,5,6,7 phys_nid=0,1,2,3
Socket 1: DFID=4,5,6,7 phys_nid=16,17,18,19
...
2-socket, sparse DFID from F5x180 (Model 0x06):
Socket 0: DFID=4,5,8,9 phys_nid=0,1,2,3
Socket 1: DFID=4,5,8,9 phys_nid=16,17,18,19
== Design ==
=== DF Node Enumeration ===
hygon_build_cache() runs lazily on the first API call, under a mutex,
and publishes the completed cache via smp_store_release(ready).
Phase 1 -- Collect:
Walk all DF misc (F3) devices matching hygon_nb_misc_ids[]. For each:
- Find the F4 (link) sibling on the same PCI slot.
- Read F1x200 via the F1 sibling: socket_id and die ID.
- On Model 0x06-0x08: read F5x180 via the F5 sibling for the real
DFID (MyDieId != DFID on these models).
- A model-to-device-ID table (hygon_df_table[]) maps boot CPU model
to the expected F1/F5 device IDs, avoiding per-model switch-case
logic and making new-model support a one-row table addition.
Validate that socket IDs are dense (0..N-1).
Phase 2 -- Sort and classify:
Sort all collected entries by (is_cdd DESC, socket_id ASC, dfid ASC).
Count num_cdd. Validate: num_cdd > 0, fits in u8, divisible by
num_sockets, each socket contributes the same CDD count.
After sorting, the node array is partitioned as:
nodes[]
+----------------------------------------------------------+
| CDD region: indices 0 .. num_cdd-1 |
| sorted by (socket_id ASC, dfid ASC) |
| index = logical_node_id |
| |
| [0] sock=0 dfid=4 logical 0 -> amd_nb[0] |
| [1] sock=0 dfid=5 logical 1 -> amd_nb[1] |
| ... |
| [7] sock=1 dfid=7 logical 7 -> amd_nb[7] |
+----------------------------------------------------------+
| IOD region: indices num_cdd .. num_nodes-1 |
| socket_id used for SMN root assignment only |
| |
| [8] sock=0 dfid=0 IOD -> amd_roots[8] |
| [9] sock=1 dfid=0 IOD -> amd_roots[9] |
+----------------------------------------------------------+
IOD nodes must be present because amd_roots[] is indexed by the full
node count (CDD + IOD), and SMN access via amd_smn_read() indexes
amd_roots[] directly by node number.
=== CPU-to-Node Mapping ===
The array index in the CDD region serves as the logical_node_id.
Consumers such as EDAC, MCE, and ATL need to translate a CPU reference
to this index. The challenge is bridging two independent hardware worlds:
DF world: socket_id and DFID from PCI config registers (F1x200, F5x180)
CPU world: phys_node_id from CPUID 8000001Eh[7:0] per core
Both are hardware-fixed values. Neither depends on software enumeration
order. A third hardware property connects them:
Within a socket, CDDs with ascending DFID are assigned ascending
phys_node_id values. Lower socket IDs always occupy lower phys_nid
ranges.
Phase 3 of the cache build exploits this:
- Collect unique phys_node_id values from online CPUs (one per CDD)
via topology_amd_node_id(), using a bitmap for de-duplication.
- Sort the collected values globally ascending.
- Map: nid_to_logical[nids[i]] = i for i in 0..num_cdd-1.
Since sorted_nodes[] CDD and sorted_nids[] both follow the same physical
ordering, nids[i] and nodes[i] correspond to the same physical die. The
result is a stable, hardware-anchored bijection that any consumer can
use by calling hygon_cpu_to_logical_node(cpu).
The mapping is stored as a 256-byte direct-mapped array for O(1) lookup:
index: 0 1 2 3 4 ... 15 16 17 18 19 20 ... 255
+---+---+---+---+---+ +---+---+---+---+---+---+ +---+
value: | 0 | 1 | 2 | 3 |FF | .. |FF | 4 | 5 | 6 | 7 |FF | .. |FF |
+---+---+---+---+---+ +---+---+---+---+---+---+ +---+
^ ^ ^ ^ ^ ^ ^ ^
sock0 logical 0-3 sock1 logical 4-7
If the hardware property is violated on a future platform, Phase 3
validation detects it (collected phys_nid count != num_cdd) and fails
loudly rather than producing a silently wrong mapping.
=== SMN Access ===
SMN root devices (PCI class 0x0600, vendor 0x1d94) are already
enumerated and reserved by amd_smn_init() in amd_node.c -- the existing
get_next_root() already matches the Hygon vendor ID. The only difference
from AMD is root-to-node assignment:
AMD: one root per node (roots_per_node grouping)
Hygon: one root per socket, shared across all nodes on that socket
(roots_per_socket grouping, expanded via hygon_node_socket())
Three internal functions hook into amd_node.c (not exported; used only
by built-in x86 code):
hygon_node_num() -- total node count, sizes amd_nb[] and amd_roots[]
hygon_socket_num() -- socket count, for roots_per_socket
hygon_node_socket(n) -- socket ID for node n, for amd_roots[] fill
Two further internal functions hook into amd_nb.c:
hygon_node_get_func(n, 3/4) -- PCI dev for DF misc/link of node n
== API ==
Exported (for loadable module consumers: EDAC, MCE, ATL):
hygon_f18h_model() model byte, 0 if not Hygon Family 0x18
hygon_cdd_num() CDD count (EDAC instance sizing)
hygon_get_dfid(misc, &dfid) DFID for a DF misc device
hygon_cpu_to_logical_node(cpu) logical node ID (0..N-1), or -errno
Inline helpers in the header (outside CONFIG_AMD_NODE guard):
hygon_f18h_m4h() -- model in 0x04..0x0f
hygon_f18h_m10h() -- model >= 0x10
asm/hygon/node.h provides static inline stubs returning 0/NULL/-ENODEV
for all functions when CONFIG_AMD_NODE=n; no #ifdef guards are needed
in consumer code.
== Series ==
[1/5] pci_ids: Add Hygon Family 0x18 DF device IDs
PCI device IDs for DF function 1, 3, 4, and 5 siblings across
all supported Hygon Family 0x18 models.
[2/5] x86/hygon: Add Family 0x18 node enumeration API header
asm/hygon/node.h: API declarations, CONFIG_AMD_NODE=n stubs, and
inline model-check helpers.
[3/5] x86/hygon: Add DF misc-based node enumeration helpers
Core implementation: three-phase cache build, hardware-anchored
CPU-to-node mapping, and all public/internal API functions.
[4/5] x86/amd_nb: Use Hygon DF misc enumeration for Family 0x18
amd_cache_northbridges() uses hygon_node_num() and
hygon_node_get_func() on Hygon systems to size and populate
amd_northbridges[].
[5/5] x86/amd_node: Support Hygon SMN roots by socket
amd_smn_init() gains a Hygon branch: per-socket root grouping
and per-node expansion via hygon_node_socket(). AMD behavior is
unchanged.
== Testing ==
Verified boot, SMN access, and CPU-to-node mapping correctness on:
- Hygon Family 0x18 Model 0x04, 4-socket (16 CDD + 4 IOD)
- Hygon Family 0x18 Model 0x05, 2-socket (8 CDD + 2 IOD)
- Hygon Family 0x18 Model 0x06, 2-socket, sparse DFID (8 CDD + 2 IOD)
== Feedback Requested ==
- Is the three-phase enumeration approach the right structure, or
should Hygon node discovery be folded into amd_node.c more directly?
- Is the hardware-anchored bijection (Phase 3) a sound way to
establish the CPU-to-CDD mapping, or is there a cleaner mechanism
that does not rely on the DFID-ASC <-> phys_nid-ASC property?
- Does the exported API (four functions) provide the right abstraction
for EDAC/MCE/ATL consumers?
- Is the per-socket SMN root assignment by PCI enumeration order safe
to rely on, given that root devices carry no hardware socket ID?
- This series intentionally minimises changes to existing AMD code
(amd_nb.c, amd_node.c), preferring narrow hooks over deeper
integration. If the community sees a cleaner or more maintainable
approach to integrating Hygon DF support into the AMD node framework,
suggestions are welcome.
Lin Wang (5):
pci_ids: Add Hygon Family 0x18 DF device IDs
x86/hygon: Add Family 0x18 node enumeration API header
x86/hygon: Add DF misc-based node enumeration helpers
x86/amd_nb: Use Hygon DF misc enumeration for Family 0x18
x86/amd_node: Support Hygon SMN roots by socket
MAINTAINERS | 2 +
arch/x86/include/asm/hygon/node.h | 148 ++++++
arch/x86/kernel/Makefile | 2 +-
arch/x86/kernel/amd_nb.c | 17 +-
arch/x86/kernel/amd_node.c | 122 ++++-
arch/x86/kernel/hygon_node.c | 721 ++++++++++++++++++++++++++++++
include/linux/pci_ids.h | 4 +
7 files changed, 997 insertions(+), 19 deletions(-)
create mode 100644 arch/x86/include/asm/hygon/node.h
create mode 100644 arch/x86/kernel/hygon_node.c
Link: https://lore.kernel.org/all/20241206161210.163701-1-yazen.ghannam@amd.com/ # [1]
Link: https://lore.kernel.org/all/20250107222847.3300430-1-yazen.ghannam@amd.com/ # [2]
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [RFC PATCH 1/5] pci_ids: Add Hygon Family 0x18 DF device IDs
2026-04-02 11:15 [RFC PATCH 00/05] x86/hygon: Add Family 0x18 node enumeration and SMN access Lin Wang
@ 2026-04-02 11:15 ` Lin Wang
2026-04-02 17:56 ` Mario Limonciello
2026-04-02 11:15 ` [RFC PATCH 2/5] x86/hygon: Add Family 0x18 node enumeration API header Lin Wang
` (3 subsequent siblings)
4 siblings, 1 reply; 12+ messages in thread
From: Lin Wang @ 2026-04-02 11:15 UTC (permalink / raw)
To: yazen.ghannam, mario.limonciello, Borislav Petkov
Cc: tglx, mingo, x86, linux-kernel
Add the PCI device IDs required to discover Hygon Family 0x18 Data Fabric
functions.
Hygon systems expose DF-related PCI functions using PCI_VENDOR_ID_HYGON.
Some numeric device IDs are shared with AMD families but are qualified by
PCI_VENDOR_ID_HYGON when used on Hygon systems.
Signed-off-by: Lin Wang <wanglin@open-hieco.net>
---
include/linux/pci_ids.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index 406abf629be2..0b51773412be 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -568,8 +568,10 @@
#define PCI_DEVICE_ID_AMD_16H_M30H_NB_F3 0x1583
#define PCI_DEVICE_ID_AMD_16H_M30H_NB_F4 0x1584
#define PCI_DEVICE_ID_AMD_17H_DF_F3 0x1463
+#define PCI_DEVICE_ID_AMD_17H_DF_F4 0x1464
#define PCI_DEVICE_ID_AMD_17H_M10H_DF_F3 0x15eb
#define PCI_DEVICE_ID_AMD_17H_M30H_DF_F3 0x1493
+#define PCI_DEVICE_ID_AMD_17H_M30H_DF_F4 0x1494
#define PCI_DEVICE_ID_AMD_17H_M40H_DF_F3 0x13f3
#define PCI_DEVICE_ID_AMD_17H_M60H_DF_F3 0x144b
#define PCI_DEVICE_ID_AMD_17H_M70H_DF_F3 0x1443
@@ -2613,6 +2615,8 @@
#define PCI_VENDOR_ID_ROCKCHIP 0x1d87
#define PCI_VENDOR_ID_HYGON 0x1d94
+#define PCI_DEVICE_ID_HYGON_18H_M05H_DF_F3 0x14b3
+#define PCI_DEVICE_ID_HYGON_18H_M10H_DF_F3 0x14d3
#define PCI_VENDOR_ID_META 0x1d9b
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [RFC PATCH 2/5] x86/hygon: Add Family 0x18 node enumeration API header
2026-04-02 11:15 [RFC PATCH 00/05] x86/hygon: Add Family 0x18 node enumeration and SMN access Lin Wang
2026-04-02 11:15 ` [RFC PATCH 1/5] pci_ids: Add Hygon Family 0x18 DF device IDs Lin Wang
@ 2026-04-02 11:15 ` Lin Wang
2026-04-02 11:15 ` [RFC PATCH 3/5] x86/hygon: Add DF misc-based node enumeration helpers Lin Wang
` (2 subsequent siblings)
4 siblings, 0 replies; 12+ messages in thread
From: Lin Wang @ 2026-04-02 11:15 UTC (permalink / raw)
To: yazen.ghannam, mario.limonciello, Borislav Petkov
Cc: tglx, mingo, x86, linux-kernel
Introduce a small Hygon-only node helper header.
The header provides prototypes for DF misc/link accessors, node and socket
counters, DFID/family-model helpers used by external users, and CPU/channel
translation helpers shared by amd_nb, EDAC, and ATL code. It is built under
CONFIG_AMD_NODE and consumed without extending AMD-only data structures.
Signed-off-by: Lin Wang <wanglin@open-hieco.net>
---
MAINTAINERS | 3 +
arch/x86/include/asm/hygon/node.h | 148 ++++++++++++++++++++++++++++++
2 files changed, 151 insertions(+)
create mode 100644 arch/x86/include/asm/hygon/node.h
diff --git a/MAINTAINERS b/MAINTAINERS
index c3fe46d7c4bc..2a6cf33a34e2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11865,8 +11865,11 @@ F: drivers/input/touchscreen/hycon-hy46xx.c
HYGON PROCESSOR SUPPORT
M: Pu Wen <puwen@hygon.cn>
+M: Lin Wang <wanglin@open-hieco.net>
L: linux-kernel@vger.kernel.org
S: Maintained
+F: arch/x86/include/asm/hygon/
+F: arch/x86/kernel/hygon_node.c
F: arch/x86/kernel/cpu/hygon.c
HYNIX HI556 SENSOR DRIVER
diff --git a/arch/x86/include/asm/hygon/node.h b/arch/x86/include/asm/hygon/node.h
new file mode 100644
index 000000000000..fce9ff8ba27d
--- /dev/null
+++ b/arch/x86/include/asm/hygon/node.h
@@ -0,0 +1,148 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _ASM_X86_HYGON_NODE_H
+#define _ASM_X86_HYGON_NODE_H
+
+#include <linux/errno.h>
+#include <linux/types.h>
+
+#include <asm/processor.h>
+#include <asm/topology.h>
+
+struct pci_dev;
+
+#define HYGON_MAX_SOCKETS 8
+
+#ifdef CONFIG_AMD_NODE
+
+/**
+ * hygon_node_num() - total node entries (CDD + IOD)
+ *
+ * Hygon Fam18h enumerates nodes by walking DF misc (F3) devices rather than
+ * using the fixed 00:18..1f scheme used by AMD. Returns the total count of
+ * CDD and IOD nodes, which sizes amd_nb[] and amd_roots[].
+ */
+u16 hygon_node_num(void);
+
+/**
+ * hygon_cdd_num() - number of compute dies (CDD)
+ *
+ * DF IDs >= 4 represent compute dies (CDD). DF IDs < 4 represent IO dies
+ * (IOD). EDAC instances must be sized by CDD count, not total node count,
+ * because IOD nodes have no UMC.
+ */
+u16 hygon_cdd_num(void);
+
+/**
+ * hygon_socket_num() - number of sockets
+ */
+u16 hygon_socket_num(void);
+
+/**
+ * hygon_f18h_model() - return Hygon Fam18h model byte, or 0 if not Hygon Fam18h
+ */
+u8 hygon_f18h_model(void);
+
+/**
+ * hygon_node_get_func() - get PCI dev for a Hygon node DF function
+ * @node: logical node index (0..hygon_node_num()-1)
+ * @func: PCI function number (3 for DF misc, 4 for DF link)
+ *
+ * Returns a reference-counted pci_dev pointer. Caller must call
+ * pci_dev_put() when done.
+ */
+struct pci_dev *hygon_node_get_func(u16 node, u8 func);
+
+/**
+ * hygon_node_socket() - get socket ID for a logical node
+ * @node: logical node index (0..hygon_node_num()-1)
+ *
+ * Returns the hardware socket ID for @node (0..hygon_socket_num()-1).
+ * Used by amd_smn_init() to assign per-socket SMN root devices to the
+ * per-node amd_roots[] array. Returns U8_MAX on error.
+ */
+u8 hygon_node_socket(u16 node);
+
+/**
+ * hygon_get_dfid() - read DF ID for a Hygon DF misc device
+ * @misc: PCI dev for DF misc (function 3)
+ * @dfid: output DF ID
+ */
+int hygon_get_dfid(struct pci_dev *misc, u8 *dfid);
+
+/**
+ * hygon_cpu_to_logical_node() - map CPU to dense logical node ID (O(1))
+ * @cpu: CPU index
+ *
+ * Hygon Fam18h exposes sparse physical node IDs (CPUID 8000001E[7:0]).
+ * This function translates the per-CPU physical node ID into a contiguous
+ * logical node ID (0..hygon_cdd_num()-1) that aligns with EDAC instance
+ * numbering and amd_nb[] indexing.
+ *
+ * Return: logical node id on success, negative errno on failure.
+ */
+int hygon_cpu_to_logical_node(unsigned int cpu);
+
+#else /* !CONFIG_AMD_NODE */
+static inline u16 hygon_node_num(void)
+{
+ return 0;
+}
+
+static inline u16 hygon_cdd_num(void)
+{
+ return 0;
+}
+
+static inline u16 hygon_socket_num(void)
+{
+ return 0;
+}
+
+static inline u8 hygon_f18h_model(void)
+{
+ return 0;
+}
+
+static inline struct pci_dev *hygon_node_get_func(u16 node, u8 func)
+{
+ return NULL;
+}
+
+static inline u8 hygon_node_socket(u16 node)
+{
+ return U8_MAX;
+}
+
+static inline int hygon_get_dfid(struct pci_dev *misc, u8 *dfid)
+{
+ return -ENODEV;
+}
+
+static inline int hygon_cpu_to_logical_node(unsigned int cpu)
+{
+ return -ENODEV;
+}
+#endif /* CONFIG_AMD_NODE */
+
+/**
+ * hygon_f18h_m4h() - check Hygon family 18h model 4h..fh
+ */
+static inline bool hygon_f18h_m4h(void)
+{
+ u8 m = hygon_f18h_model();
+
+ return m >= 0x4 && m <= 0xf;
+}
+
+/**
+ * hygon_f18h_m10h() - check Hygon family 18h model 10h..1fh
+ */
+static inline bool hygon_f18h_m10h(void)
+{
+ u8 m = hygon_f18h_model();
+
+ return m >= 0x10;
+}
+
+
+#endif /* _ASM_X86_HYGON_NODE_H */
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [RFC PATCH 3/5] x86/hygon: Add DF misc-based node enumeration helpers
2026-04-02 11:15 [RFC PATCH 00/05] x86/hygon: Add Family 0x18 node enumeration and SMN access Lin Wang
2026-04-02 11:15 ` [RFC PATCH 1/5] pci_ids: Add Hygon Family 0x18 DF device IDs Lin Wang
2026-04-02 11:15 ` [RFC PATCH 2/5] x86/hygon: Add Family 0x18 node enumeration API header Lin Wang
@ 2026-04-02 11:15 ` Lin Wang
2026-04-02 18:50 ` Mario Limonciello
2026-04-02 11:15 ` [RFC PATCH 4/5] x86/amd_nb: Use Hygon DF misc enumeration for Family 0x18 Lin Wang
2026-04-02 11:15 ` [RFC PATCH 5/5] x86/amd_node: Support Hygon SMN roots by socket Lin Wang
4 siblings, 1 reply; 12+ messages in thread
From: Lin Wang @ 2026-04-02 11:15 UTC (permalink / raw)
To: yazen.ghannam, mario.limonciello, Borislav Petkov
Cc: tglx, mingo, x86, linux-kernel
Hygon Family 0x18 systems cannot rely on the fixed PCI slot layout
(00:18..1f) used by AMD node enumeration. Enumerate DF "misc" (F3) and
link (F4) devices by PCI IDs and build a cached view of nodes, sockets,
and roots.
The cache keeps:
- DF misc/link devices ordered as "CDD first" (DFID >= 4) and "IOD tail"
(DFID < 4).
- A per-node node->socket map.
- One root device per socket for SMN routing and the full list of
matching roots for PCI config space reservation.
CPUID 8000001E[7:0] (phys_node_id) is sparse across sockets; all
consumers need a stable dense 0..num_cdd-1 index. Both the DF-register
ordering (socket_id, dfid) and globally sorted phys_node_ids follow the
same physical progression, so the i-th entry in each sorted sequence
refers to the same die. nid_to_logical[phys_nid] stores this
hardware-anchored mapping for O(1) lookup.
Provide a small set of Hygon-only helpers that can be consumed by
amd_nb/amd_node code without extending AMD-only structs.
Signed-off-by: Lin Wang <wanglin@open-hieco.net>
---
arch/x86/kernel/Makefile | 2 +-
arch/x86/kernel/hygon_node.c | 721 +++++++++++++++++++++++++++++++++++
2 files changed, 722 insertions(+), 1 deletion(-)
create mode 100644 arch/x86/kernel/hygon_node.c
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
index e9aeeeafad17..44a0e429214d 100644
--- a/arch/x86/kernel/Makefile
+++ b/arch/x86/kernel/Makefile
@@ -121,7 +121,7 @@ obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
obj-$(CONFIG_HPET_TIMER) += hpet.o
obj-$(CONFIG_AMD_NB) += amd_nb.o
-obj-$(CONFIG_AMD_NODE) += amd_node.o
+obj-$(CONFIG_AMD_NODE) += amd_node.o hygon_node.o
obj-$(CONFIG_DEBUG_NMI_SELFTEST) += nmi_selftest.o
obj-$(CONFIG_KVM_GUEST) += kvm.o kvmclock.o
diff --git a/arch/x86/kernel/hygon_node.c b/arch/x86/kernel/hygon_node.c
new file mode 100644
index 000000000000..e3a103ed3f93
--- /dev/null
+++ b/arch/x86/kernel/hygon_node.c
@@ -0,0 +1,721 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Hygon Fam18h Node helper functions
+ *
+ * Hygon Fam18h does not follow AMD's fixed 00:18..1f per-node PCI slot
+ * layout. Enumerate real DF misc/link devices via PCI ID matching, read
+ * hardware identity from per-instance DF registers (F1x200, F5x180), and
+ * build a dense logical node ID shared by NB, EDAC, MCE and ATL consumers.
+ */
+
+#define pr_fmt(fmt) "hygon_node: " fmt
+
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/cpu.h>
+#include <linux/export.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/slab.h>
+#include <linux/sort.h>
+
+#include <asm/hygon/node.h>
+#include <asm/processor.h>
+#include <asm/topology.h>
+
+/* CPUID 8000001E[7:0] is 8-bit and globally unique. */
+#define HYGON_MAX_PHYS_NID 256
+/* Sentinel for unmapped entries in nid_to_logical[]. */
+#define HYGON_NID_INVALID U8_MAX
+
+/*
+ * DF register offsets used for node identity discovery.
+ *
+ * F1x200 (SystemCfg) -- present on all models:
+ * [30:28] MySocketId - hardware socket ID
+ * [23:20] MyDieId - die ID (equals DFID on some models, see below)
+ *
+ * F5x180 (FabricBlockInstanceInformation3_CS) -- Model 06h-08h only:
+ * [19:16] DFID - real Data Fabric ID for UMC/SMN addressing
+ *
+ * DFID source by model:
+ * Model 04h/05h: F1x200[23:20] (MyDieId == DFID)
+ * Model 06h-08h: F5x180[19:16] (MyDieId != DFID, different numbering)
+ * Model 10h+: F1x200[23:20] (MyDieId == DFID, same as Model 04h/05h)
+ */
+#define DF_F1_SYSTEM_CFG 0x200
+#define DF_F5_FABRIC_ID 0x180
+
+/* DF function numbers for sibling device access. */
+#define HYGON_DF_F1 1 /* SystemCfg: socket and die identity */
+#define HYGON_DF_F3 3 /* Miscellaneous (misc) */
+#define HYGON_DF_F4 4 /* Link */
+#define HYGON_DF_F5 5 /* FabricId: real DFID on Model 06h-08h */
+
+/* DF sibling device IDs used only within this file for identity reads. */
+#define PCI_DEVICE_ID_HYGON_18H_M04H_DF_F1 0x1491
+#define PCI_DEVICE_ID_HYGON_18H_M05H_DF_F1 0x14b1
+#define PCI_DEVICE_ID_HYGON_18H_M05H_DF_F4 0x14b4
+#define PCI_DEVICE_ID_HYGON_18H_M06H_DF_F5 0x14b5
+#define PCI_DEVICE_ID_HYGON_18H_M10H_DF_F4 0x14d4
+
+/* Cached identity for one DF instance. After sorting, CDDs occupy nodes[0..num_cdd-1]. */
+struct hygon_node {
+ struct pci_dev *misc; /* DF function 3 */
+ struct pci_dev *link; /* DF function 4 */
+ u8 socket_id; /* F1x200[30:28] */
+ u8 dfid; /* model-dependent DFID */
+ bool is_cdd; /* DFID >= 4 */
+};
+
+struct hygon_node_cache {
+ struct hygon_node *nodes; /* sorted: CDD first, then IOD */
+ u16 num_nodes; /* CDD + IOD = amd_nb[]/amd_roots[] size */
+ u16 num_cdd; /* CDD only = EDAC instance count */
+ u16 num_sockets;
+
+ /*
+ * Direct sparse-to-dense mapping. nid_to_logical[phys_nid] gives the
+ * dense logical_node_id (0..num_cdd-1), or HYGON_NID_INVALID.
+ */
+ u8 nid_to_logical[HYGON_MAX_PHYS_NID];
+
+ bool ready;
+};
+
+struct hygon_df_id {
+ u8 socket_id;
+ u8 dfid;
+};
+
+/* Model-specific DF sibling device IDs for reading node identity. */
+struct hygon_df_func_ids {
+ u8 model_start;
+ u8 model_end;
+ u16 f1_id;
+ u16 f5_id; /* 0 = not available */
+};
+
+/*
+ * DF misc (F3) device IDs for all supported Hygon Fam18h models.
+ * Model 04h silicon shares device IDs with AMD Family 17h; the Hygon vendor
+ * ID is the discriminator. Models 05h-08h and 10h+ have Hygon-specific IDs.
+ */
+static const struct pci_device_id hygon_nb_misc_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) }, /* M04h */
+ { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_AMD_17H_M30H_DF_F3) }, /* M04h variant */
+ { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_M05H_DF_F3) }, /* M05h-08h */
+ { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_M10H_DF_F3) }, /* M10h+ */
+ {}
+};
+
+/* DF link (F4) device IDs, parallel to hygon_nb_misc_ids[]. */
+static const struct pci_device_id hygon_nb_link_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_AMD_17H_DF_F4) }, /* M04h */
+ { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_AMD_17H_M30H_DF_F4) }, /* M04h variant */
+ { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_M05H_DF_F4) }, /* M05h-08h */
+ { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_M10H_DF_F4) }, /* M10h+ */
+ {}
+};
+
+/*
+ * Map Hygon Fam18h model ranges onto the DF sibling functions used to read
+ * node identity. Model 5 may still expose the Model 4 F1 device id on mixed
+ * silicon, which is handled separately in hygon_read_df_reg().
+ */
+static const struct hygon_df_func_ids hygon_df_table[] = {
+ { 0x04, 0x04, PCI_DEVICE_ID_HYGON_18H_M04H_DF_F1, 0 },
+ { 0x05, 0x05, PCI_DEVICE_ID_HYGON_18H_M05H_DF_F1, 0 },
+ { 0x06, 0x08, PCI_DEVICE_ID_HYGON_18H_M05H_DF_F1,
+ PCI_DEVICE_ID_HYGON_18H_M06H_DF_F5 },
+ { 0x10, 0x1f, PCI_DEVICE_ID_HYGON_18H_M05H_DF_F1, 0 },
+ {}
+};
+
+static struct hygon_node_cache hygon_cache;
+static DEFINE_MUTEX(hygon_mutex);
+
+static void hygon_log_nodes(const struct hygon_node_cache *cache, const char *phase)
+{
+ u16 i;
+
+ pr_debug("%s: %u nodes\n", phase, cache->num_nodes);
+
+ for (i = 0; i < cache->num_nodes; i++) {
+ const struct hygon_node *node = &cache->nodes[i];
+
+ pr_debug("%s: node[%u] %04x:%02x:%02x.%u socket=%u dfid=%u type=%s\n",
+ phase, i, pci_domain_nr(node->misc->bus), node->misc->bus->number,
+ PCI_SLOT(node->misc->devfn), PCI_FUNC(node->misc->devfn),
+ node->socket_id, node->dfid, node->is_cdd ? "CDD" : "IOD");
+ }
+}
+
+/*
+ * Iterate Hygon PCI devices, returning the next one that matches @ids.
+ * Follows the pci_get_device() convention: @from is consumed (its reference
+ * is dropped) and the returned device has an elevated reference count.
+ */
+static struct pci_dev *next_hygon_dev(struct pci_dev *from,
+ const struct pci_device_id *ids)
+{
+ while ((from = pci_get_device(PCI_VENDOR_ID_HYGON, PCI_ANY_ID, from))) {
+ if (pci_match_id(ids, from))
+ return from;
+ }
+
+ return NULL;
+}
+
+/*
+ * Find the DF link (function 4) sibling of a DF misc (function 3) device.
+ * Both functions share the same PCI bus and slot.
+ */
+static struct pci_dev *hygon_get_link(struct pci_dev *misc)
+{
+ struct pci_dev *link;
+
+ link = pci_get_domain_bus_and_slot(pci_domain_nr(misc->bus),
+ misc->bus->number,
+ PCI_DEVFN(PCI_SLOT(misc->devfn), HYGON_DF_F4));
+ if (!link)
+ return NULL;
+
+ if (!pci_match_id(hygon_nb_link_ids, link)) {
+ pci_dev_put(link);
+ return NULL;
+ }
+
+ return link;
+}
+
+static const struct hygon_df_func_ids *hygon_get_df_func_ids(void)
+{
+ const struct hygon_df_func_ids *entry;
+ u8 model = boot_cpu_data.x86_model;
+
+ for (entry = hygon_df_table; entry->f1_id; entry++) {
+ if (model >= entry->model_start && model <= entry->model_end)
+ return entry;
+ }
+
+ pr_warn_once("unsupported Hygon Fam18h model 0x%x, Hygon node support disabled\n",
+ model);
+ return NULL;
+}
+
+/*
+ * Read a config register from a DF sibling function on the same PCI slot as
+ * @misc. Only functions 1 (F1, SystemCfg) and 5 (F5, FabricId) are
+ * supported.
+ */
+static int hygon_read_df_reg(struct pci_dev *misc, u8 func, int offset,
+ u32 *value)
+{
+ const struct hygon_df_func_ids *ids;
+ struct pci_dev *sibling;
+ u16 expected_device;
+ int err;
+
+ ids = hygon_get_df_func_ids();
+ if (!ids)
+ return -ENODEV;
+
+ if (func == HYGON_DF_F1) {
+ expected_device = ids->f1_id;
+
+ /*
+ * Model 5 can expose an older mixed-silicon variant where the
+ * F1 sibling still uses the M04H device ID.
+ */
+ if (boot_cpu_data.x86_model == 0x5 &&
+ misc->device != PCI_DEVICE_ID_HYGON_18H_M05H_DF_F3)
+ expected_device = PCI_DEVICE_ID_HYGON_18H_M04H_DF_F1;
+ } else if (func == HYGON_DF_F5) {
+ expected_device = ids->f5_id;
+ } else {
+ return -EINVAL;
+ }
+
+ if (!expected_device)
+ return -ENODEV;
+
+ sibling = pci_get_domain_bus_and_slot(pci_domain_nr(misc->bus),
+ misc->bus->number,
+ PCI_DEVFN(PCI_SLOT(misc->devfn),
+ func));
+ if (!sibling)
+ return -ENODEV;
+
+ if (sibling->vendor != PCI_VENDOR_ID_HYGON ||
+ sibling->device != expected_device) {
+ pci_dev_put(sibling);
+ return -ENODEV;
+ }
+
+ err = pci_read_config_dword(sibling, offset, value);
+ pci_dev_put(sibling);
+
+ if (err)
+ pr_warn("error reading %04x:%02x:%02x.%u offset 0x%x\n",
+ pci_domain_nr(misc->bus), misc->bus->number,
+ PCI_SLOT(misc->devfn), func, offset);
+
+ return pcibios_err_to_errno(err);
+}
+
+/*
+ * Read the hardware identity for one DF misc device from its sibling
+ * functions.
+ *
+ * All models expose F1x200 (SystemCfg): socket_id from [30:28] and a die
+ * identifier (MyDieId) from [23:20].
+ * On Model 06h-08h MyDieId differs from the DFID used by UMC and SMN
+ * addressing, so an additional F5x180 (FabricId) read is required to
+ * obtain the real DFID from [19:16].
+ */
+static int hygon_read_df_id(struct pci_dev *misc, struct hygon_df_id *id)
+{
+ u32 reg;
+ int ret;
+
+ ret = hygon_read_df_reg(misc, HYGON_DF_F1, DF_F1_SYSTEM_CFG, ®);
+ if (ret)
+ return ret;
+
+ id->socket_id = (reg >> 28) & 0x7;
+ id->dfid = (reg >> 20) & 0xf;
+
+ /*
+ * All DF instances on a Hygon system are the same model, so
+ * boot_cpu_data.x86_model is representative for all devices.
+ */
+ if (boot_cpu_data.x86_model >= 0x6 &&
+ boot_cpu_data.x86_model <= 0x8) {
+ ret = hygon_read_df_reg(misc, HYGON_DF_F5, DF_F5_FABRIC_ID, ®);
+ if (ret)
+ return ret;
+ id->dfid = (reg >> 16) & 0xf;
+ }
+
+ return 0;
+}
+
+static void hygon_release_nodes(struct hygon_node *nodes, u16 count)
+{
+ u16 i;
+
+ for (i = 0; i < count; i++) {
+ pci_dev_put(nodes[i].misc);
+ pci_dev_put(nodes[i].link);
+ }
+
+ kfree(nodes);
+}
+
+static int hygon_collect_nodes(struct hygon_node_cache *cache)
+{
+ struct hygon_node *nodes;
+ struct pci_dev *misc;
+ u16 capacity = 0, count = 0;
+ u8 socket_mask = 0;
+ int ret;
+
+ misc = NULL;
+ while ((misc = next_hygon_dev(misc, hygon_nb_misc_ids)))
+ capacity++;
+
+ if (!capacity)
+ return -ENODEV;
+
+ nodes = kcalloc(capacity, sizeof(*nodes), GFP_KERNEL);
+ if (!nodes)
+ return -ENOMEM;
+
+ misc = NULL;
+ while ((misc = next_hygon_dev(misc, hygon_nb_misc_ids))) {
+ struct hygon_df_id id;
+ struct pci_dev *link;
+
+ link = hygon_get_link(misc);
+ if (!link) {
+ pci_dev_put(misc);
+ ret = -ENODEV;
+ goto fail;
+ }
+
+ ret = hygon_read_df_id(misc, &id);
+ if (ret) {
+ pci_dev_put(link);
+ pci_dev_put(misc);
+ goto fail;
+ }
+
+ if (count >= capacity) {
+ pci_dev_put(link);
+ pci_dev_put(misc);
+ ret = -ENODEV;
+ goto fail;
+ }
+
+ pr_debug("collect: %04x:%02x:%02x.%u socket=%u dfid=%u\n",
+ pci_domain_nr(misc->bus), misc->bus->number,
+ PCI_SLOT(misc->devfn), PCI_FUNC(misc->devfn),
+ id.socket_id, id.dfid);
+
+ nodes[count].misc = pci_dev_get(misc);
+ nodes[count].link = link;
+ nodes[count].socket_id = id.socket_id;
+ nodes[count].dfid = id.dfid;
+ nodes[count].is_cdd = (id.dfid >= 4);
+ socket_mask |= BIT(id.socket_id);
+ count++;
+ }
+
+ if (!count) {
+ ret = -ENODEV;
+ goto fail;
+ }
+
+ cache->nodes = nodes;
+ cache->num_nodes = count;
+ cache->num_sockets = hweight8(socket_mask);
+
+ if (!cache->num_sockets ||
+ socket_mask != GENMASK(cache->num_sockets - 1, 0)) {
+ pr_warn("sparse socket IDs not supported (mask=0x%x)\n",
+ socket_mask);
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ hygon_release_nodes(nodes, count);
+ cache->nodes = NULL;
+ cache->num_nodes = 0;
+ return ret;
+}
+
+/* Sort CDD nodes before IOD nodes, then order by hardware (socket_id, dfid). */
+static int hygon_node_cmp(const void *a, const void *b)
+{
+ const struct hygon_node *left = a;
+ const struct hygon_node *right = b;
+
+ if (left->is_cdd != right->is_cdd)
+ return right->is_cdd - left->is_cdd;
+
+ if (left->socket_id != right->socket_id)
+ return (int)left->socket_id - right->socket_id;
+
+ return (int)left->dfid - (int)right->dfid;
+}
+
+/*
+ * Classify the sorted node array and validate the CDD layout.
+ *
+ * Hygon EDAC sizing assumes each socket contributes the same number of compute
+ * dies, so reject topologies that do not satisfy that invariant.
+ */
+static int hygon_sort_and_classify(struct hygon_node_cache *cache)
+{
+ u16 cdd_per_socket;
+ u16 i;
+ u8 per_sock_count[HYGON_MAX_SOCKETS] = { 0 };
+
+ hygon_log_nodes(cache, "before-sort");
+
+ sort(cache->nodes, cache->num_nodes, sizeof(*cache->nodes),
+ hygon_node_cmp, NULL);
+
+ for (i = 0; i < cache->num_nodes; i++) {
+ if (!cache->nodes[i].is_cdd)
+ break;
+ }
+
+ cache->num_cdd = i;
+
+ if (!cache->num_cdd)
+ return -ENODEV;
+
+ if (cache->num_cdd > HYGON_NID_INVALID) {
+ pr_warn("CDD count %u exceeds u8 logical ID range\n",
+ cache->num_cdd);
+ return -EOVERFLOW;
+ }
+
+ if (cache->num_cdd % cache->num_sockets) {
+ pr_warn("CDD count %u not divisible by %u sockets\n",
+ cache->num_cdd, cache->num_sockets);
+ return -EINVAL;
+ }
+
+ cdd_per_socket = cache->num_cdd / cache->num_sockets;
+ if (!cdd_per_socket)
+ return -EINVAL;
+
+ for (i = 0; i < cache->num_cdd; i++) {
+ u8 socket_id = cache->nodes[i].socket_id;
+
+ if (socket_id >= cache->num_sockets)
+ return -EINVAL;
+ per_sock_count[socket_id]++;
+ }
+
+ for (i = 0; i < cache->num_sockets; i++) {
+ if (per_sock_count[i] != cdd_per_socket) {
+ pr_warn("socket %u: %u CDDs, expected %u\n",
+ i, per_sock_count[i], cdd_per_socket);
+ return -EINVAL;
+ }
+ }
+
+ hygon_log_nodes(cache, "after-sort");
+
+ return 0;
+}
+
+static int hygon_u8_cmp(const void *a, const void *b)
+{
+ return (int)*(const u8 *)a - (int)*(const u8 *)b;
+}
+
+/*
+ * Build nid_to_logical[]: collect unique phys_node_id values from online CPUs,
+ * sort them globally ascending, then write nid_to_logical[phys_nids[i]] = i.
+ *
+ * This works because both orderings follow the same physical progression:
+ * nodes[] CDD region: (socket_id ASC, dfid ASC) from DF registers
+ * phys_nids[]: globally ascending; lower socket always occupies a
+ * lower phys_nid range, and within a socket ascending
+ * DFID order corresponds to ascending phys_nid order.
+ *
+ * A local bitmap (nid_seen) de-duplicates CPUs that share a node; each
+ * unique phys_nid is collected exactly once into phys_nids[]. The resulting
+ * runtime lookup is O(1): a direct array access by phys_nid.
+ */
+static int hygon_build_nid_map(struct hygon_node_cache *cache)
+{
+ DECLARE_BITMAP(nid_seen, HYGON_MAX_PHYS_NID) = { };
+ u8 *phys_nids;
+ u16 count = 0;
+ u16 i;
+ int cpu;
+
+ phys_nids = kcalloc(cache->num_cdd, sizeof(*phys_nids), GFP_KERNEL);
+ if (!phys_nids)
+ return -ENOMEM;
+
+ for_each_online_cpu(cpu) {
+ unsigned int phys_nid = topology_amd_node_id(cpu);
+
+ if (phys_nid >= HYGON_MAX_PHYS_NID) {
+ pr_warn("cpu %u: phys_node_id %u out of range\n",
+ cpu, phys_nid);
+ kfree(phys_nids);
+ return -ERANGE;
+ }
+
+ if (__test_and_set_bit(phys_nid, nid_seen))
+ continue;
+
+ if (count >= cache->num_cdd) {
+ pr_warn("more unique phys_node_ids than CDD nodes\n");
+ kfree(phys_nids);
+ return -EINVAL;
+ }
+
+ phys_nids[count++] = (u8)phys_nid;
+ }
+
+ if (count != cache->num_cdd) {
+ pr_warn("collected %u phys_node_ids, expected %u CDDs\n",
+ count, cache->num_cdd);
+ kfree(phys_nids);
+ return -EINVAL;
+ }
+
+ sort(phys_nids, count, sizeof(*phys_nids), hygon_u8_cmp, NULL);
+
+ memset(cache->nid_to_logical, HYGON_NID_INVALID,
+ sizeof(cache->nid_to_logical));
+
+ for (i = 0; i < count; i++)
+ cache->nid_to_logical[phys_nids[i]] = i;
+
+ kfree(phys_nids);
+ return 0;
+}
+
+static void hygon_destroy_cache(struct hygon_node_cache *cache)
+{
+ if (cache->nodes)
+ hygon_release_nodes(cache->nodes, cache->num_nodes);
+
+ cache->nodes = NULL;
+ cache->num_nodes = 0;
+ cache->num_cdd = 0;
+ cache->num_sockets = 0;
+ memset(cache->nid_to_logical, HYGON_NID_INVALID,
+ sizeof(cache->nid_to_logical));
+ cache->ready = false;
+}
+
+/*
+ * Build and publish the global Hygon node cache.
+ *
+ * Uses a double-checked locking pattern: the first smp_load_acquire() on
+ * @ready provides a fast lockless path for all calls after the initial build.
+ * The second check inside the mutex prevents duplicate construction if two
+ * callers race through the first check simultaneously.
+ *
+ * On success, smp_store_release() on @ready ensures all cache writes are
+ * visible to subsequent smp_load_acquire() readers without a lock.
+ */
+static int hygon_build_cache(void)
+{
+ struct hygon_node_cache tmp;
+ int err;
+
+ /* Pairs with smp_store_release() below; fast path once cache is built. */
+ if (smp_load_acquire(&hygon_cache.ready))
+ return 0;
+
+ if (boot_cpu_data.x86_vendor != X86_VENDOR_HYGON ||
+ boot_cpu_data.x86 != 0x18)
+ return -ENODEV;
+
+ guard(mutex)(&hygon_mutex);
+
+ /* Re-check under mutex to handle concurrent builders. */
+ if (smp_load_acquire(&hygon_cache.ready))
+ return 0;
+
+ memset(&tmp, 0, sizeof(tmp));
+ memset(tmp.nid_to_logical, HYGON_NID_INVALID,
+ sizeof(tmp.nid_to_logical));
+
+ err = hygon_collect_nodes(&tmp);
+ if (err)
+ goto fail;
+
+ err = hygon_sort_and_classify(&tmp);
+ if (err)
+ goto fail;
+
+ err = hygon_build_nid_map(&tmp);
+ if (err)
+ goto fail;
+
+ hygon_cache = tmp;
+ /* Pairs with smp_load_acquire() above; ensures cache is visible to all CPUs. */
+ smp_store_release(&hygon_cache.ready, true);
+ return 0;
+
+fail:
+ hygon_destroy_cache(&tmp);
+ return err;
+}
+
+u8 hygon_f18h_model(void)
+{
+ if (boot_cpu_data.x86_vendor == X86_VENDOR_HYGON &&
+ boot_cpu_data.x86 == 0x18)
+ return boot_cpu_data.x86_model;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hygon_f18h_model);
+
+int hygon_get_dfid(struct pci_dev *misc, u8 *dfid)
+{
+ struct hygon_df_id id;
+ int ret;
+
+ ret = hygon_read_df_id(misc, &id);
+ if (ret)
+ return ret;
+
+ *dfid = id.dfid;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hygon_get_dfid);
+
+u16 hygon_node_num(void)
+{
+ return hygon_build_cache() ? 0 : hygon_cache.num_nodes;
+}
+
+u16 hygon_cdd_num(void)
+{
+ return hygon_build_cache() ? 0 : hygon_cache.num_cdd;
+}
+EXPORT_SYMBOL_GPL(hygon_cdd_num);
+
+u16 hygon_socket_num(void)
+{
+ return hygon_build_cache() ? 0 : hygon_cache.num_sockets;
+}
+
+/*
+ * Return the DF function device for a logical Hygon node.
+ * The caller must call pci_dev_put() on the returned pointer when done.
+ * Only functions 3 (misc) and 4 (link) are supported.
+ */
+struct pci_dev *hygon_node_get_func(u16 node, u8 func)
+{
+ if (hygon_build_cache())
+ return NULL;
+
+ if (node >= hygon_cache.num_nodes)
+ return NULL;
+
+ switch (func) {
+ case HYGON_DF_F3:
+ return pci_dev_get(hygon_cache.nodes[node].misc);
+ case HYGON_DF_F4:
+ return pci_dev_get(hygon_cache.nodes[node].link);
+ default:
+ return NULL;
+ }
+}
+
+u8 hygon_node_socket(u16 node)
+{
+ if (hygon_build_cache())
+ return U8_MAX;
+
+ if (node >= hygon_cache.num_nodes)
+ return U8_MAX;
+
+ return hygon_cache.nodes[node].socket_id;
+}
+
+/*
+ * Translate a CPU's sparse physical node ID (CPUID 8000001E[7:0]) into the
+ * dense logical node ID (0..hygon_cdd_num()-1) used by NB, EDAC, MCE, and
+ * ATL. The lookup is O(1) via direct array access on nid_to_logical[].
+ *
+ * Returns the logical node ID on success, or a negative errno on failure.
+ */
+int hygon_cpu_to_logical_node(unsigned int cpu)
+{
+ unsigned int phys_nid;
+ u8 logical_id;
+
+ if (hygon_build_cache())
+ return -ENODEV;
+
+ phys_nid = topology_amd_node_id(cpu);
+ if (phys_nid >= HYGON_MAX_PHYS_NID)
+ return -ENODEV;
+
+ logical_id = hygon_cache.nid_to_logical[phys_nid];
+ return logical_id == HYGON_NID_INVALID ? -ENODEV : logical_id;
+}
+EXPORT_SYMBOL_GPL(hygon_cpu_to_logical_node);
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [RFC PATCH 4/5] x86/amd_nb: Use Hygon DF misc enumeration for Family 0x18
2026-04-02 11:15 [RFC PATCH 00/05] x86/hygon: Add Family 0x18 node enumeration and SMN access Lin Wang
` (2 preceding siblings ...)
2026-04-02 11:15 ` [RFC PATCH 3/5] x86/hygon: Add DF misc-based node enumeration helpers Lin Wang
@ 2026-04-02 11:15 ` Lin Wang
2026-04-02 11:15 ` [RFC PATCH 5/5] x86/amd_node: Support Hygon SMN roots by socket Lin Wang
4 siblings, 0 replies; 12+ messages in thread
From: Lin Wang @ 2026-04-02 11:15 UTC (permalink / raw)
To: yazen.ghannam, mario.limonciello, Borislav Petkov
Cc: tglx, mingo, x86, linux-kernel
On Hygon Family 0x18 systems, DF devices cannot be enumerated through
the fixed PCI slot layout used by AMD systems.
Use the Hygon node helpers to size the northbridge array and to obtain DF
misc/link devices for NB initialization.
Signed-off-by: Lin Wang <wanglin@open-hieco.net>
---
arch/x86/kernel/amd_nb.c | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/arch/x86/kernel/amd_nb.c b/arch/x86/kernel/amd_nb.c
index 5d364540673d..77d166a17154 100644
--- a/arch/x86/kernel/amd_nb.c
+++ b/arch/x86/kernel/amd_nb.c
@@ -16,6 +16,8 @@
#include <asm/amd/nb.h>
#include <asm/cpuid/api.h>
+#include <asm/hygon/node.h>
+#include <asm/processor.h>
static u32 *flush_words;
@@ -60,13 +62,20 @@ EXPORT_SYMBOL_GPL(node_to_amd_nb);
static int amd_cache_northbridges(void)
{
+ bool is_hygon = (boot_cpu_data.x86_vendor == X86_VENDOR_HYGON);
struct amd_northbridge *nb;
u16 i;
if (amd_northbridges.num)
return 0;
- amd_northbridges.num = amd_num_nodes();
+ if (is_hygon) {
+ amd_northbridges.num = hygon_node_num();
+ if (!amd_northbridges.num)
+ return -ENODEV;
+ } else {
+ amd_northbridges.num = amd_num_nodes();
+ }
nb = kzalloc_objs(struct amd_northbridge, amd_northbridges.num);
if (!nb)
@@ -75,7 +84,8 @@ static int amd_cache_northbridges(void)
amd_northbridges.nb = nb;
for (i = 0; i < amd_northbridges.num; i++) {
- node_to_amd_nb(i)->misc = amd_node_get_func(i, 3);
+ node_to_amd_nb(i)->misc = is_hygon ? hygon_node_get_func(i, 3)
+ : amd_node_get_func(i, 3);
/*
* Each Northbridge must have a 'misc' device.
@@ -87,7 +97,8 @@ static int amd_cache_northbridges(void)
return -ENODEV;
}
- node_to_amd_nb(i)->link = amd_node_get_func(i, 4);
+ node_to_amd_nb(i)->link = is_hygon ? hygon_node_get_func(i, 4)
+ : amd_node_get_func(i, 4);
}
if (amd_gart_present())
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [RFC PATCH 5/5] x86/amd_node: Support Hygon SMN roots by socket
2026-04-02 11:15 [RFC PATCH 00/05] x86/hygon: Add Family 0x18 node enumeration and SMN access Lin Wang
` (3 preceding siblings ...)
2026-04-02 11:15 ` [RFC PATCH 4/5] x86/amd_nb: Use Hygon DF misc enumeration for Family 0x18 Lin Wang
@ 2026-04-02 11:15 ` Lin Wang
2026-04-02 23:50 ` Borislav Petkov
4 siblings, 1 reply; 12+ messages in thread
From: Lin Wang @ 2026-04-02 11:15 UTC (permalink / raw)
To: yazen.ghannam, mario.limonciello, Borislav Petkov
Cc: tglx, mingo, x86, linux-kernel
SMN root devices (PCI class 0x0600, vendor 0x1d94) are already
enumerated by get_next_root(), which already matches the Hygon vendor
ID. The difference from AMD is root-to-node assignment:
AMD: one root per node (roots_per_node grouping)
Hygon: one root per socket, shared across all nodes on that socket
Extend amd_smn_init() with a Hygon branch that:
- uses hygon_node_num() and hygon_socket_num() to determine the node
and socket counts from the hygon_node cache
- groups the discovered roots by socket (roots_per_socket)
- expands them to per-node entries in amd_roots[] using
hygon_node_socket()
Introduce smn_num_nodes to hold the per-vendor node count, so that
__amd_smn_rw() and the debugfs bounds check work correctly for both
AMD and Hygon system layouts.
AMD behavior is unchanged.
Signed-off-by: Lin Wang <wanglin@open-hieco.net>
---
arch/x86/kernel/amd_node.c | 122 +++++++++++++++++++++++++++++++------
1 file changed, 102 insertions(+), 20 deletions(-)
diff --git a/arch/x86/kernel/amd_node.c b/arch/x86/kernel/amd_node.c
index 0be01725a2a4..61c7b8542197 100644
--- a/arch/x86/kernel/amd_node.c
+++ b/arch/x86/kernel/amd_node.c
@@ -9,7 +9,9 @@
*/
#include <linux/debugfs.h>
+#include <asm/processor.h>
#include <asm/amd/node.h>
+#include <asm/hygon/node.h>
/*
* AMD Nodes are a physical collection of I/O devices within an SoC. There can be one
@@ -35,6 +37,7 @@ struct pci_dev *amd_node_get_func(u16 node, u8 func)
}
static struct pci_dev **amd_roots;
+static u16 smn_num_nodes;
/* Protect the PCI config register pairs used for SMN. */
static DEFINE_MUTEX(smn_mutex);
@@ -88,7 +91,7 @@ static int __amd_smn_rw(u8 i_off, u8 d_off, u16 node, u32 address, u32 *value, b
struct pci_dev *root;
int err = -ENODEV;
- if (node >= amd_num_nodes())
+ if (node >= smn_num_nodes)
return err;
root = amd_roots[node];
@@ -151,7 +154,7 @@ static ssize_t smn_node_write(struct file *file, const char __user *userbuf,
if (ret)
return ret;
- if (node >= amd_num_nodes())
+ if (node >= smn_num_nodes)
return -ENODEV;
debug_node = node;
@@ -246,10 +249,14 @@ __setup("amd_smn_debugfs_enable", amd_smn_enable_dfs);
static int __init amd_smn_init(void)
{
- u16 count, num_roots, roots_per_node, node, num_nodes;
+ struct pci_dev *socket_roots[HYGON_MAX_SOCKETS] = { };
+ u16 count, num_roots, roots_per_node, roots_per_socket, node, num_nodes;
+ u16 num_sockets, socket, socket_id;
struct pci_dev *root;
+ int ret;
- if (!cpu_feature_enabled(X86_FEATURE_ZEN))
+ if (!cpu_feature_enabled(X86_FEATURE_ZEN) &&
+ boot_cpu_data.x86_vendor != X86_VENDOR_HYGON)
return 0;
guard(mutex)(&smn_mutex);
@@ -268,7 +275,9 @@ static int __init amd_smn_init(void)
* entire PCI config space for simplicity rather than covering
* specific registers piecemeal.
*/
- if (!pci_request_config_region_exclusive(root, 0, PCI_CFG_SPACE_SIZE, NULL)) {
+ if (!pci_request_config_region_exclusive(root, 0,
+ PCI_CFG_SPACE_SIZE,
+ NULL)) {
pci_err(root, "Failed to reserve config space\n");
return -EEXIST;
}
@@ -276,30 +285,97 @@ static int __init amd_smn_init(void)
num_roots++;
}
- pr_debug("Found %d AMD root devices\n", num_roots);
+ pr_debug("Found %d SMN root devices\n", num_roots);
if (!num_roots)
return -ENODEV;
- num_nodes = amd_num_nodes();
- amd_roots = kzalloc_objs(*amd_roots, num_nodes);
- if (!amd_roots)
- return -ENOMEM;
+ if (boot_cpu_data.x86_vendor == X86_VENDOR_HYGON) {
+ /*
+ * Hygon: roots are shared per-socket, not one-per-node.
+ * Build amd_roots[] by expanding socket roots to per-node
+ * using hygon_node_socket() for the assignment.
+ *
+ * hygon_node_num() triggers hygon_build_cache() which reads
+ * hardware identity registers and validates socket topology.
+ */
+ socket = 0;
+
+ num_nodes = hygon_node_num();
+ if (!num_nodes)
+ return -ENODEV;
- roots_per_node = num_roots / num_nodes;
+ num_sockets = hygon_socket_num();
+ if (!num_sockets)
+ return -ENODEV;
- count = 0;
- node = 0;
- root = NULL;
- while (node < num_nodes && (root = get_next_root(root))) {
- /* Use one root for each node and skip the rest. */
- if (count++ % roots_per_node)
- continue;
+ if (num_sockets > ARRAY_SIZE(socket_roots))
+ return -EINVAL;
- pci_dbg(root, "is root for AMD node %u\n", node);
- amd_roots[node++] = root;
+ if (num_roots % num_sockets) {
+ pr_err("Root count %u not divisible by socket count %u\n",
+ num_roots, num_sockets);
+ return -ENODEV;
+ }
+
+ smn_num_nodes = num_nodes;
+ amd_roots = kzalloc_objs(*amd_roots, num_nodes);
+ if (!amd_roots)
+ return -ENOMEM;
+
+ roots_per_socket = num_roots / num_sockets;
+ count = 0;
+ root = NULL;
+ while (socket < num_sockets && (root = get_next_root(root))) {
+ if (count++ % roots_per_socket)
+ continue;
+
+ pci_dbg(root, "is root for Hygon socket %u\n", socket);
+ socket_roots[socket++] = root;
+ }
+
+ if (socket != num_sockets) {
+ ret = -ENODEV;
+ goto err_free;
+ }
+
+ for (node = 0; node < num_nodes; node++) {
+ socket_id = hygon_node_socket(node);
+
+ if (socket_id >= num_sockets) {
+ ret = -ENODEV;
+ goto err_free;
+ }
+
+ pci_dbg(socket_roots[socket_id],
+ "is root for Hygon node %u (socket %u)\n",
+ node, socket_id);
+ amd_roots[node] = socket_roots[socket_id];
+ }
+
+ } else {
+ num_nodes = amd_num_nodes();
+ smn_num_nodes = num_nodes;
+ amd_roots = kzalloc_objs(*amd_roots, num_nodes);
+ if (!amd_roots)
+ return -ENOMEM;
+
+ roots_per_node = num_roots / num_nodes;
+
+ count = 0;
+ node = 0;
+ root = NULL;
+ while (node < num_nodes && (root = get_next_root(root))) {
+ /* Use one root for each node and skip the rest. */
+ if (count++ % roots_per_node)
+ continue;
+
+ pci_dbg(root, "is root for AMD node %u\n", node);
+ amd_roots[node++] = root;
+ }
}
+
if (enable_dfs) {
debugfs_dir = debugfs_create_dir("amd_smn", arch_debugfs_dir);
@@ -311,6 +387,12 @@ static int __init amd_smn_init(void)
smn_exclusive = true;
return 0;
+
+err_free:
+ kfree(amd_roots);
+ amd_roots = NULL;
+ smn_num_nodes = 0;
+ return ret;
}
fs_initcall(amd_smn_init);
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH 1/5] pci_ids: Add Hygon Family 0x18 DF device IDs
2026-04-02 11:15 ` [RFC PATCH 1/5] pci_ids: Add Hygon Family 0x18 DF device IDs Lin Wang
@ 2026-04-02 17:56 ` Mario Limonciello
2026-04-03 3:39 ` Lin Wang
0 siblings, 1 reply; 12+ messages in thread
From: Mario Limonciello @ 2026-04-02 17:56 UTC (permalink / raw)
To: Lin Wang, yazen.ghannam, Borislav Petkov; +Cc: tglx, mingo, x86, linux-kernel
On 4/2/26 6:15 AM, Lin Wang wrote:
> [Some people who received this message don't often get email from wanglin@open-hieco.net. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>
> Add the PCI device IDs required to discover Hygon Family 0x18 Data Fabric
> functions.
>
> Hygon systems expose DF-related PCI functions using PCI_VENDOR_ID_HYGON.
> Some numeric device IDs are shared with AMD families but are qualified by
> PCI_VENDOR_ID_HYGON when used on Hygon systems.
I don't think this is a good idea to start precedent of using exact same
define for Hygon and AMD because it might not always be true.
Could we instead have something like
#define PCI_DEVICE_ID_HYGON_17H_M30H_DF_F3 PCI_DEVICE_ID_AMD_17H_M30H_DF_F3
And then anywhere you're using it like you describe (such as how I see
in patch 3) then you can use PCI_DEVICE_ID_HYGON_17H_M30H_DF_F3 instead.
>
> Signed-off-by: Lin Wang <wanglin@open-hieco.net>
> ---
> include/linux/pci_ids.h | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
> index 406abf629be2..0b51773412be 100644
> --- a/include/linux/pci_ids.h
> +++ b/include/linux/pci_ids.h
> @@ -568,8 +568,10 @@
> #define PCI_DEVICE_ID_AMD_16H_M30H_NB_F3 0x1583
> #define PCI_DEVICE_ID_AMD_16H_M30H_NB_F4 0x1584
> #define PCI_DEVICE_ID_AMD_17H_DF_F3 0x1463
> +#define PCI_DEVICE_ID_AMD_17H_DF_F4 0x1464
> #define PCI_DEVICE_ID_AMD_17H_M10H_DF_F3 0x15eb
> #define PCI_DEVICE_ID_AMD_17H_M30H_DF_F3 0x1493
> +#define PCI_DEVICE_ID_AMD_17H_M30H_DF_F4 0x1494
> #define PCI_DEVICE_ID_AMD_17H_M40H_DF_F3 0x13f3
> #define PCI_DEVICE_ID_AMD_17H_M60H_DF_F3 0x144b
> #define PCI_DEVICE_ID_AMD_17H_M70H_DF_F3 0x1443
> @@ -2613,6 +2615,8 @@
> #define PCI_VENDOR_ID_ROCKCHIP 0x1d87
>
> #define PCI_VENDOR_ID_HYGON 0x1d94
> +#define PCI_DEVICE_ID_HYGON_18H_M05H_DF_F3 0x14b3
> +#define PCI_DEVICE_ID_HYGON_18H_M10H_DF_F3 0x14d3
>
> #define PCI_VENDOR_ID_META 0x1d9b
>
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH 3/5] x86/hygon: Add DF misc-based node enumeration helpers
2026-04-02 11:15 ` [RFC PATCH 3/5] x86/hygon: Add DF misc-based node enumeration helpers Lin Wang
@ 2026-04-02 18:50 ` Mario Limonciello
2026-04-03 5:15 ` Lin Wang
0 siblings, 1 reply; 12+ messages in thread
From: Mario Limonciello @ 2026-04-02 18:50 UTC (permalink / raw)
To: Lin Wang, yazen.ghannam, Borislav Petkov; +Cc: tglx, mingo, x86, linux-kernel
On 4/2/26 6:15 AM, Lin Wang wrote:
> [Some people who received this message don't often get email from wanglin@open-hieco.net. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>
> Hygon Family 0x18 systems cannot rely on the fixed PCI slot layout
> (00:18..1f) used by AMD node enumeration. Enumerate DF "misc" (F3) and
> link (F4) devices by PCI IDs and build a cached view of nodes, sockets,
> and roots.
>
> The cache keeps:
>
> - DF misc/link devices ordered as "CDD first" (DFID >= 4) and "IOD tail"
> (DFID < 4).
>
> - A per-node node->socket map.
>
> - One root device per socket for SMN routing and the full list of
> matching roots for PCI config space reservation.
>
> CPUID 8000001E[7:0] (phys_node_id) is sparse across sockets; all
> consumers need a stable dense 0..num_cdd-1 index. Both the DF-register
> ordering (socket_id, dfid) and globally sorted phys_node_ids follow the
> same physical progression, so the i-th entry in each sorted sequence
> refers to the same die. nid_to_logical[phys_nid] stores this
> hardware-anchored mapping for O(1) lookup.
>
> Provide a small set of Hygon-only helpers that can be consumed by
> amd_nb/amd_node code without extending AMD-only structs.
>
> Signed-off-by: Lin Wang <wanglin@open-hieco.net>
> ---
> arch/x86/kernel/Makefile | 2 +-
> arch/x86/kernel/hygon_node.c | 721 +++++++++++++++++++++++++++++++++++
A lot of helpers just for Hygon - do we perhaps want CONFIG_HYGON_NODE?
> 2 files changed, 722 insertions(+), 1 deletion(-)
> create mode 100644 arch/x86/kernel/hygon_node.c
>
> diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
> index e9aeeeafad17..44a0e429214d 100644
> --- a/arch/x86/kernel/Makefile
> +++ b/arch/x86/kernel/Makefile
> @@ -121,7 +121,7 @@ obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
> obj-$(CONFIG_HPET_TIMER) += hpet.o
>
> obj-$(CONFIG_AMD_NB) += amd_nb.o
> -obj-$(CONFIG_AMD_NODE) += amd_node.o
> +obj-$(CONFIG_AMD_NODE) += amd_node.o hygon_node.o
> obj-$(CONFIG_DEBUG_NMI_SELFTEST) += nmi_selftest.o
>
> obj-$(CONFIG_KVM_GUEST) += kvm.o kvmclock.o
> diff --git a/arch/x86/kernel/hygon_node.c b/arch/x86/kernel/hygon_node.c
> new file mode 100644
> index 000000000000..e3a103ed3f93
> --- /dev/null
> +++ b/arch/x86/kernel/hygon_node.c
> @@ -0,0 +1,721 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Hygon Fam18h Node helper functions
> + *
> + * Hygon Fam18h does not follow AMD's fixed 00:18..1f per-node PCI slot
> + * layout. Enumerate real DF misc/link devices via PCI ID matching, read
> + * hardware identity from per-instance DF registers (F1x200, F5x180), and
> + * build a dense logical node ID shared by NB, EDAC, MCE and ATL consumers.
> + */
> +
> +#define pr_fmt(fmt) "hygon_node: " fmt
> +
> +#include <linux/bitmap.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/cpu.h>
> +#include <linux/export.h>
> +#include <linux/init.h>
> +#include <linux/mutex.h>
> +#include <linux/pci.h>
> +#include <linux/pci_ids.h>
> +#include <linux/slab.h>
> +#include <linux/sort.h>
> +
> +#include <asm/hygon/node.h>
> +#include <asm/processor.h>
> +#include <asm/topology.h>
> +
> +/* CPUID 8000001E[7:0] is 8-bit and globally unique. */
> +#define HYGON_MAX_PHYS_NID 256
> +/* Sentinel for unmapped entries in nid_to_logical[]. */
> +#define HYGON_NID_INVALID U8_MAX
> +
> +/*
> + * DF register offsets used for node identity discovery.
> + *
> + * F1x200 (SystemCfg) -- present on all models:
> + * [30:28] MySocketId - hardware socket ID
> + * [23:20] MyDieId - die ID (equals DFID on some models, see below)
> + *
> + * F5x180 (FabricBlockInstanceInformation3_CS) -- Model 06h-08h only:
> + * [19:16] DFID - real Data Fabric ID for UMC/SMN addressing
> + *
> + * DFID source by model:
> + * Model 04h/05h: F1x200[23:20] (MyDieId == DFID)
> + * Model 06h-08h: F5x180[19:16] (MyDieId != DFID, different numbering)
> + * Model 10h+: F1x200[23:20] (MyDieId == DFID, same as Model 04h/05h)
> + */
> +#define DF_F1_SYSTEM_CFG 0x200
> +#define DF_F5_FABRIC_ID 0x180
> +
> +/* DF function numbers for sibling device access. */
> +#define HYGON_DF_F1 1 /* SystemCfg: socket and die identity */
> +#define HYGON_DF_F3 3 /* Miscellaneous (misc) */
> +#define HYGON_DF_F4 4 /* Link */
> +#define HYGON_DF_F5 5 /* FabricId: real DFID on Model 06h-08h */
> +
> +/* DF sibling device IDs used only within this file for identity reads. */
> +#define PCI_DEVICE_ID_HYGON_18H_M04H_DF_F1 0x1491
> +#define PCI_DEVICE_ID_HYGON_18H_M05H_DF_F1 0x14b1
> +#define PCI_DEVICE_ID_HYGON_18H_M05H_DF_F4 0x14b4
> +#define PCI_DEVICE_ID_HYGON_18H_M06H_DF_F5 0x14b5
> +#define PCI_DEVICE_ID_HYGON_18H_M10H_DF_F4 0x14d4
> +
> +/* Cached identity for one DF instance. After sorting, CDDs occupy nodes[0..num_cdd-1]. */
> +struct hygon_node {
> + struct pci_dev *misc; /* DF function 3 */
> + struct pci_dev *link; /* DF function 4 */
> + u8 socket_id; /* F1x200[30:28] */
> + u8 dfid; /* model-dependent DFID */
> + bool is_cdd; /* DFID >= 4 */
> +};
> +
> +struct hygon_node_cache {
> + struct hygon_node *nodes; /* sorted: CDD first, then IOD */
> + u16 num_nodes; /* CDD + IOD = amd_nb[]/amd_roots[] size */
> + u16 num_cdd; /* CDD only = EDAC instance count */
> + u16 num_sockets;
> +
> + /*
> + * Direct sparse-to-dense mapping. nid_to_logical[phys_nid] gives the
> + * dense logical_node_id (0..num_cdd-1), or HYGON_NID_INVALID.
> + */
> + u8 nid_to_logical[HYGON_MAX_PHYS_NID];
> +
> + bool ready;
> +};
> +
> +struct hygon_df_id {
> + u8 socket_id;
> + u8 dfid;
> +};
> +
> +/* Model-specific DF sibling device IDs for reading node identity. */
> +struct hygon_df_func_ids {
> + u8 model_start;
> + u8 model_end;
> + u16 f1_id;
> + u16 f5_id; /* 0 = not available */
> +};
> +
> +/*
> + * DF misc (F3) device IDs for all supported Hygon Fam18h models.
> + * Model 04h silicon shares device IDs with AMD Family 17h; the Hygon vendor
> + * ID is the discriminator. Models 05h-08h and 10h+ have Hygon-specific IDs.
> + */
> +static const struct pci_device_id hygon_nb_misc_ids[] = {
> + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) }, /* M04h */
> + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_AMD_17H_M30H_DF_F3) }, /* M04h variant */
> + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_M05H_DF_F3) }, /* M05h-08h */
> + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_M10H_DF_F3) }, /* M10h+ */
> + {}
> +};
> +
> +/* DF link (F4) device IDs, parallel to hygon_nb_misc_ids[]. */
> +static const struct pci_device_id hygon_nb_link_ids[] = {
> + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_AMD_17H_DF_F4) }, /* M04h */
> + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_AMD_17H_M30H_DF_F4) }, /* M04h variant */
> + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_M05H_DF_F4) }, /* M05h-08h */
> + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_M10H_DF_F4) }, /* M10h+ */
> + {}
> +};
> +
> +/*
> + * Map Hygon Fam18h model ranges onto the DF sibling functions used to read
> + * node identity. Model 5 may still expose the Model 4 F1 device id on mixed
> + * silicon, which is handled separately in hygon_read_df_reg().
> + */
> +static const struct hygon_df_func_ids hygon_df_table[] = {
> + { 0x04, 0x04, PCI_DEVICE_ID_HYGON_18H_M04H_DF_F1, 0 },
> + { 0x05, 0x05, PCI_DEVICE_ID_HYGON_18H_M05H_DF_F1, 0 },
> + { 0x06, 0x08, PCI_DEVICE_ID_HYGON_18H_M05H_DF_F1,
> + PCI_DEVICE_ID_HYGON_18H_M06H_DF_F5 },
> + { 0x10, 0x1f, PCI_DEVICE_ID_HYGON_18H_M05H_DF_F1, 0 },
> + {}
> +};
> +
> +static struct hygon_node_cache hygon_cache;
> +static DEFINE_MUTEX(hygon_mutex);
> +
> +static void hygon_log_nodes(const struct hygon_node_cache *cache, const char *phase)
> +{
> + u16 i;
> +
> + pr_debug("%s: %u nodes\n", phase, cache->num_nodes);
> +
> + for (i = 0; i < cache->num_nodes; i++) {
> + const struct hygon_node *node = &cache->nodes[i];
> +
> + pr_debug("%s: node[%u] %04x:%02x:%02x.%u socket=%u dfid=%u type=%s\n",
> + phase, i, pci_domain_nr(node->misc->bus), node->misc->bus->number,
> + PCI_SLOT(node->misc->devfn), PCI_FUNC(node->misc->devfn),
> + node->socket_id, node->dfid, node->is_cdd ? "CDD" : "IOD");
> + }
> +}
> +
> +/*
> + * Iterate Hygon PCI devices, returning the next one that matches @ids.
> + * Follows the pci_get_device() convention: @from is consumed (its reference
> + * is dropped) and the returned device has an elevated reference count.
> + */
> +static struct pci_dev *next_hygon_dev(struct pci_dev *from,
> + const struct pci_device_id *ids)
> +{
> + while ((from = pci_get_device(PCI_VENDOR_ID_HYGON, PCI_ANY_ID, from))) {
> + if (pci_match_id(ids, from))
> + return from;
> + }
> +
> + return NULL;
> +}
> +
> +/*
> + * Find the DF link (function 4) sibling of a DF misc (function 3) device.
> + * Both functions share the same PCI bus and slot.
> + */
> +static struct pci_dev *hygon_get_link(struct pci_dev *misc)
> +{
> + struct pci_dev *link;
> +
> + link = pci_get_domain_bus_and_slot(pci_domain_nr(misc->bus),
> + misc->bus->number,
> + PCI_DEVFN(PCI_SLOT(misc->devfn), HYGON_DF_F4));
> + if (!link)
> + return NULL;
> +
> + if (!pci_match_id(hygon_nb_link_ids, link)) {
> + pci_dev_put(link);
> + return NULL;
> + }
> +
> + return link;
> +}
> +
> +static const struct hygon_df_func_ids *hygon_get_df_func_ids(void)
> +{
> + const struct hygon_df_func_ids *entry;
> + u8 model = boot_cpu_data.x86_model;
> +
> + for (entry = hygon_df_table; entry->f1_id; entry++) {
> + if (model >= entry->model_start && model <= entry->model_end)
> + return entry;
> + }
> +
> + pr_warn_once("unsupported Hygon Fam18h model 0x%x, Hygon node support disabled\n",
> + model);
> + return NULL;
> +}
> +
> +/*
> + * Read a config register from a DF sibling function on the same PCI slot as
> + * @misc. Only functions 1 (F1, SystemCfg) and 5 (F5, FabricId) are
> + * supported.
> + */
> +static int hygon_read_df_reg(struct pci_dev *misc, u8 func, int offset,
> + u32 *value)
> +{
> + const struct hygon_df_func_ids *ids;
> + struct pci_dev *sibling;
> + u16 expected_device;
> + int err;
> +
> + ids = hygon_get_df_func_ids();
> + if (!ids)
> + return -ENODEV;
> +
> + if (func == HYGON_DF_F1) {
> + expected_device = ids->f1_id;
> +
> + /*
> + * Model 5 can expose an older mixed-silicon variant where the
> + * F1 sibling still uses the M04H device ID.
> + */
> + if (boot_cpu_data.x86_model == 0x5 &&
> + misc->device != PCI_DEVICE_ID_HYGON_18H_M05H_DF_F3)
> + expected_device = PCI_DEVICE_ID_HYGON_18H_M04H_DF_F1;
> + } else if (func == HYGON_DF_F5) {
> + expected_device = ids->f5_id;
> + } else {
> + return -EINVAL;
> + }
> +
> + if (!expected_device)
> + return -ENODEV;
> +
> + sibling = pci_get_domain_bus_and_slot(pci_domain_nr(misc->bus),
> + misc->bus->number,
> + PCI_DEVFN(PCI_SLOT(misc->devfn),
> + func));
> + if (!sibling)
> + return -ENODEV;
> +
> + if (sibling->vendor != PCI_VENDOR_ID_HYGON ||
> + sibling->device != expected_device) {
> + pci_dev_put(sibling);
> + return -ENODEV;
> + }
> +
> + err = pci_read_config_dword(sibling, offset, value);
> + pci_dev_put(sibling);
> +
> + if (err)
> + pr_warn("error reading %04x:%02x:%02x.%u offset 0x%x\n",
> + pci_domain_nr(misc->bus), misc->bus->number,
> + PCI_SLOT(misc->devfn), func, offset);
> +
> + return pcibios_err_to_errno(err);
> +}
> +
> +/*
> + * Read the hardware identity for one DF misc device from its sibling
> + * functions.
> + *
> + * All models expose F1x200 (SystemCfg): socket_id from [30:28] and a die
> + * identifier (MyDieId) from [23:20].
> + * On Model 06h-08h MyDieId differs from the DFID used by UMC and SMN
> + * addressing, so an additional F5x180 (FabricId) read is required to
> + * obtain the real DFID from [19:16].
> + */
> +static int hygon_read_df_id(struct pci_dev *misc, struct hygon_df_id *id)
> +{
> + u32 reg;
> + int ret;
> +
> + ret = hygon_read_df_reg(misc, HYGON_DF_F1, DF_F1_SYSTEM_CFG, ®);
> + if (ret)
> + return ret;
> +
> + id->socket_id = (reg >> 28) & 0x7;
> + id->dfid = (reg >> 20) & 0xf;
> +
> + /*
> + * All DF instances on a Hygon system are the same model, so
> + * boot_cpu_data.x86_model is representative for all devices.
> + */
> + if (boot_cpu_data.x86_model >= 0x6 &&
> + boot_cpu_data.x86_model <= 0x8) {
> + ret = hygon_read_df_reg(misc, HYGON_DF_F5, DF_F5_FABRIC_ID, ®);
> + if (ret)
> + return ret;
> + id->dfid = (reg >> 16) & 0xf;
> + }
> +
> + return 0;
> +}
> +
> +static void hygon_release_nodes(struct hygon_node *nodes, u16 count)
> +{
> + u16 i;
> +
> + for (i = 0; i < count; i++) {
> + pci_dev_put(nodes[i].misc);
> + pci_dev_put(nodes[i].link);
> + }
> +
> + kfree(nodes);
> +}
> +
> +static int hygon_collect_nodes(struct hygon_node_cache *cache)
> +{
> + struct hygon_node *nodes;
> + struct pci_dev *misc;
> + u16 capacity = 0, count = 0;
> + u8 socket_mask = 0;
> + int ret;
> +
> + misc = NULL;
> + while ((misc = next_hygon_dev(misc, hygon_nb_misc_ids)))
> + capacity++;
> +
> + if (!capacity)
> + return -ENODEV;
> +
> + nodes = kcalloc(capacity, sizeof(*nodes), GFP_KERNEL);
> + if (!nodes)
> + return -ENOMEM;
> +
> + misc = NULL;
> + while ((misc = next_hygon_dev(misc, hygon_nb_misc_ids))) {
> + struct hygon_df_id id;
> + struct pci_dev *link;
> +
> + link = hygon_get_link(misc);
> + if (!link) {
> + pci_dev_put(misc);
> + ret = -ENODEV;
> + goto fail;
> + }
> +
> + ret = hygon_read_df_id(misc, &id);
> + if (ret) {
> + pci_dev_put(link);
> + pci_dev_put(misc);
> + goto fail;
> + }
> +
> + if (count >= capacity) {
> + pci_dev_put(link);
> + pci_dev_put(misc);
> + ret = -ENODEV;
> + goto fail;
> + }
> +
> + pr_debug("collect: %04x:%02x:%02x.%u socket=%u dfid=%u\n",
> + pci_domain_nr(misc->bus), misc->bus->number,
> + PCI_SLOT(misc->devfn), PCI_FUNC(misc->devfn),
> + id.socket_id, id.dfid);
> +
> + nodes[count].misc = pci_dev_get(misc);
> + nodes[count].link = link;
> + nodes[count].socket_id = id.socket_id;
> + nodes[count].dfid = id.dfid;
> + nodes[count].is_cdd = (id.dfid >= 4);
> + socket_mask |= BIT(id.socket_id);
> + count++;
> + }
> +
> + if (!count) {
> + ret = -ENODEV;
> + goto fail;
> + }
> +
> + cache->nodes = nodes;
> + cache->num_nodes = count;
> + cache->num_sockets = hweight8(socket_mask);
> +
> + if (!cache->num_sockets ||
> + socket_mask != GENMASK(cache->num_sockets - 1, 0)) {
> + pr_warn("sparse socket IDs not supported (mask=0x%x)\n",
> + socket_mask);
> + ret = -EINVAL;
> + goto fail;
> + }
> +
> + return 0;
> +
> +fail:
> + hygon_release_nodes(nodes, count);
> + cache->nodes = NULL;
> + cache->num_nodes = 0;
> + return ret;
> +}
> +
> +/* Sort CDD nodes before IOD nodes, then order by hardware (socket_id, dfid). */
> +static int hygon_node_cmp(const void *a, const void *b)
> +{
> + const struct hygon_node *left = a;
> + const struct hygon_node *right = b;
> +
> + if (left->is_cdd != right->is_cdd)
> + return right->is_cdd - left->is_cdd;
> +
> + if (left->socket_id != right->socket_id)
> + return (int)left->socket_id - right->socket_id;
> +
> + return (int)left->dfid - (int)right->dfid;
> +}
> +
> +/*
> + * Classify the sorted node array and validate the CDD layout.
> + *
> + * Hygon EDAC sizing assumes each socket contributes the same number of compute
> + * dies, so reject topologies that do not satisfy that invariant.
> + */
> +static int hygon_sort_and_classify(struct hygon_node_cache *cache)
> +{
> + u16 cdd_per_socket;
> + u16 i;
> + u8 per_sock_count[HYGON_MAX_SOCKETS] = { 0 };
> +
> + hygon_log_nodes(cache, "before-sort");
> +
> + sort(cache->nodes, cache->num_nodes, sizeof(*cache->nodes),
> + hygon_node_cmp, NULL);
> +
> + for (i = 0; i < cache->num_nodes; i++) {
> + if (!cache->nodes[i].is_cdd)
> + break;
> + }
> +
> + cache->num_cdd = i;
> +
> + if (!cache->num_cdd)
> + return -ENODEV;
> +
> + if (cache->num_cdd > HYGON_NID_INVALID) {
> + pr_warn("CDD count %u exceeds u8 logical ID range\n",
> + cache->num_cdd);
> + return -EOVERFLOW;
> + }
> +
> + if (cache->num_cdd % cache->num_sockets) {
> + pr_warn("CDD count %u not divisible by %u sockets\n",
> + cache->num_cdd, cache->num_sockets);
> + return -EINVAL;
> + }
> +
> + cdd_per_socket = cache->num_cdd / cache->num_sockets;
> + if (!cdd_per_socket)
> + return -EINVAL;
> +
> + for (i = 0; i < cache->num_cdd; i++) {
> + u8 socket_id = cache->nodes[i].socket_id;
> +
> + if (socket_id >= cache->num_sockets)
> + return -EINVAL;
> + per_sock_count[socket_id]++;
> + }
> +
> + for (i = 0; i < cache->num_sockets; i++) {
> + if (per_sock_count[i] != cdd_per_socket) {
> + pr_warn("socket %u: %u CDDs, expected %u\n",
> + i, per_sock_count[i], cdd_per_socket);
> + return -EINVAL;
> + }
> + }
> +
> + hygon_log_nodes(cache, "after-sort");
> +
> + return 0;
> +}
> +
> +static int hygon_u8_cmp(const void *a, const void *b)
> +{
> + return (int)*(const u8 *)a - (int)*(const u8 *)b;
> +}
> +
> +/*
> + * Build nid_to_logical[]: collect unique phys_node_id values from online CPUs,
> + * sort them globally ascending, then write nid_to_logical[phys_nids[i]] = i.
> + *
> + * This works because both orderings follow the same physical progression:
> + * nodes[] CDD region: (socket_id ASC, dfid ASC) from DF registers
> + * phys_nids[]: globally ascending; lower socket always occupies a
> + * lower phys_nid range, and within a socket ascending
> + * DFID order corresponds to ascending phys_nid order.
> + *
> + * A local bitmap (nid_seen) de-duplicates CPUs that share a node; each
> + * unique phys_nid is collected exactly once into phys_nids[]. The resulting
> + * runtime lookup is O(1): a direct array access by phys_nid.
> + */
> +static int hygon_build_nid_map(struct hygon_node_cache *cache)
> +{
> + DECLARE_BITMAP(nid_seen, HYGON_MAX_PHYS_NID) = { };
> + u8 *phys_nids;
> + u16 count = 0;
> + u16 i;
> + int cpu;
> +
> + phys_nids = kcalloc(cache->num_cdd, sizeof(*phys_nids), GFP_KERNEL);
> + if (!phys_nids)
> + return -ENOMEM;
> +
> + for_each_online_cpu(cpu) {
> + unsigned int phys_nid = topology_amd_node_id(cpu);
> +
> + if (phys_nid >= HYGON_MAX_PHYS_NID) {
> + pr_warn("cpu %u: phys_node_id %u out of range\n",
> + cpu, phys_nid);
> + kfree(phys_nids);
> + return -ERANGE;
> + }
> +
> + if (__test_and_set_bit(phys_nid, nid_seen))
> + continue;
> +
> + if (count >= cache->num_cdd) {
> + pr_warn("more unique phys_node_ids than CDD nodes\n");
> + kfree(phys_nids);
> + return -EINVAL;
> + }
> +
> + phys_nids[count++] = (u8)phys_nid;
> + }
> +
> + if (count != cache->num_cdd) {
> + pr_warn("collected %u phys_node_ids, expected %u CDDs\n",
> + count, cache->num_cdd);
> + kfree(phys_nids);
> + return -EINVAL;
> + }
> +
> + sort(phys_nids, count, sizeof(*phys_nids), hygon_u8_cmp, NULL);
> +
> + memset(cache->nid_to_logical, HYGON_NID_INVALID,
> + sizeof(cache->nid_to_logical));
> +
> + for (i = 0; i < count; i++)
> + cache->nid_to_logical[phys_nids[i]] = i;
> +
> + kfree(phys_nids);
> + return 0;
> +}
> +
> +static void hygon_destroy_cache(struct hygon_node_cache *cache)
> +{
> + if (cache->nodes)
> + hygon_release_nodes(cache->nodes, cache->num_nodes);
> +
> + cache->nodes = NULL;
> + cache->num_nodes = 0;
> + cache->num_cdd = 0;
> + cache->num_sockets = 0;
> + memset(cache->nid_to_logical, HYGON_NID_INVALID,
> + sizeof(cache->nid_to_logical));
> + cache->ready = false;
> +}
> +
> +/*
> + * Build and publish the global Hygon node cache.
> + *
> + * Uses a double-checked locking pattern: the first smp_load_acquire() on
> + * @ready provides a fast lockless path for all calls after the initial build.
> + * The second check inside the mutex prevents duplicate construction if two
> + * callers race through the first check simultaneously.
> + *
> + * On success, smp_store_release() on @ready ensures all cache writes are
> + * visible to subsequent smp_load_acquire() readers without a lock.
> + */
> +static int hygon_build_cache(void)
> +{
> + struct hygon_node_cache tmp;
> + int err;
> +
> + /* Pairs with smp_store_release() below; fast path once cache is built. */
> + if (smp_load_acquire(&hygon_cache.ready))
> + return 0;
> +
> + if (boot_cpu_data.x86_vendor != X86_VENDOR_HYGON ||
> + boot_cpu_data.x86 != 0x18)
> + return -ENODEV;
> +
> + guard(mutex)(&hygon_mutex);
> +
> + /* Re-check under mutex to handle concurrent builders. */
> + if (smp_load_acquire(&hygon_cache.ready))
> + return 0;
> +
> + memset(&tmp, 0, sizeof(tmp));
> + memset(tmp.nid_to_logical, HYGON_NID_INVALID,
> + sizeof(tmp.nid_to_logical));
> +
> + err = hygon_collect_nodes(&tmp);
> + if (err)
> + goto fail;
> +
> + err = hygon_sort_and_classify(&tmp);
> + if (err)
> + goto fail;
> +
> + err = hygon_build_nid_map(&tmp);
> + if (err)
> + goto fail;
> +
> + hygon_cache = tmp;
> + /* Pairs with smp_load_acquire() above; ensures cache is visible to all CPUs. */
> + smp_store_release(&hygon_cache.ready, true);
> + return 0;
> +
> +fail:
> + hygon_destroy_cache(&tmp);
> + return err;
> +}
> +
> +u8 hygon_f18h_model(void)
> +{
> + if (boot_cpu_data.x86_vendor == X86_VENDOR_HYGON &&
> + boot_cpu_data.x86 == 0x18)
> + return boot_cpu_data.x86_model;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(hygon_f18h_model);
> +
> +int hygon_get_dfid(struct pci_dev *misc, u8 *dfid)
> +{
> + struct hygon_df_id id;
> + int ret;
> +
> + ret = hygon_read_df_id(misc, &id);
> + if (ret)
> + return ret;
> +
> + *dfid = id.dfid;
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(hygon_get_dfid);
> +
> +u16 hygon_node_num(void)
> +{
> + return hygon_build_cache() ? 0 : hygon_cache.num_nodes;
> +}
> +
> +u16 hygon_cdd_num(void)
> +{
> + return hygon_build_cache() ? 0 : hygon_cache.num_cdd;
> +}
> +EXPORT_SYMBOL_GPL(hygon_cdd_num);
> +
> +u16 hygon_socket_num(void)
> +{
> + return hygon_build_cache() ? 0 : hygon_cache.num_sockets;
> +}
> +
> +/*
> + * Return the DF function device for a logical Hygon node.
> + * The caller must call pci_dev_put() on the returned pointer when done.
> + * Only functions 3 (misc) and 4 (link) are supported.
> + */
> +struct pci_dev *hygon_node_get_func(u16 node, u8 func)
> +{
> + if (hygon_build_cache())
> + return NULL;
> +
> + if (node >= hygon_cache.num_nodes)
> + return NULL;
> +
> + switch (func) {
> + case HYGON_DF_F3:
> + return pci_dev_get(hygon_cache.nodes[node].misc);
> + case HYGON_DF_F4:
> + return pci_dev_get(hygon_cache.nodes[node].link);
> + default:
> + return NULL;
> + }
> +}
> +
> +u8 hygon_node_socket(u16 node)
> +{
> + if (hygon_build_cache())
> + return U8_MAX;
> +
> + if (node >= hygon_cache.num_nodes)
> + return U8_MAX;
> +
> + return hygon_cache.nodes[node].socket_id;
> +}
> +
> +/*
> + * Translate a CPU's sparse physical node ID (CPUID 8000001E[7:0]) into the
> + * dense logical node ID (0..hygon_cdd_num()-1) used by NB, EDAC, MCE, and
> + * ATL. The lookup is O(1) via direct array access on nid_to_logical[].
> + *
> + * Returns the logical node ID on success, or a negative errno on failure.
> + */
> +int hygon_cpu_to_logical_node(unsigned int cpu)
> +{
> + unsigned int phys_nid;
> + u8 logical_id;
> +
> + if (hygon_build_cache())
> + return -ENODEV;
> +
> + phys_nid = topology_amd_node_id(cpu);
> + if (phys_nid >= HYGON_MAX_PHYS_NID)
> + return -ENODEV;
> +
> + logical_id = hygon_cache.nid_to_logical[phys_nid];
> + return logical_id == HYGON_NID_INVALID ? -ENODEV : logical_id;
> +}
> +EXPORT_SYMBOL_GPL(hygon_cpu_to_logical_node);
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH 5/5] x86/amd_node: Support Hygon SMN roots by socket
2026-04-02 11:15 ` [RFC PATCH 5/5] x86/amd_node: Support Hygon SMN roots by socket Lin Wang
@ 2026-04-02 23:50 ` Borislav Petkov
2026-04-03 5:29 ` Lin Wang
0 siblings, 1 reply; 12+ messages in thread
From: Borislav Petkov @ 2026-04-02 23:50 UTC (permalink / raw)
To: Lin Wang; +Cc: yazen.ghannam, mario.limonciello, tglx, mingo, x86, linux-kernel
On Thu, Apr 02, 2026 at 07:15:11PM +0800, Lin Wang wrote:
> SMN root devices (PCI class 0x0600, vendor 0x1d94) are already
> enumerated by get_next_root(), which already matches the Hygon vendor
> ID. The difference from AMD is root-to-node assignment:
>
> AMD: one root per node (roots_per_node grouping)
> Hygon: one root per socket, shared across all nodes on that socket
>
> Extend amd_smn_init() with a Hygon branch that:
> - uses hygon_node_num() and hygon_socket_num() to determine the node
> and socket counts from the hygon_node cache
> - groups the discovered roots by socket (roots_per_socket)
> - expands them to per-node entries in amd_roots[] using
> hygon_node_socket()
>
> Introduce smn_num_nodes to hold the per-vendor node count, so that
> __amd_smn_rw() and the debugfs bounds check work correctly for both
> AMD and Hygon system layouts.
>
> AMD behavior is unchanged.
>
> Signed-off-by: Lin Wang <wanglin@open-hieco.net>
> ---
> arch/x86/kernel/amd_node.c | 122 +++++++++++++++++++++++++++++++------
> 1 file changed, 102 insertions(+), 20 deletions(-)
All Hygon enablement goes into Hygon-specific files. If there's a piece of
functionality which is generic enough, then that can be extracted into
a function which can be called by both vendors code.
But pls forget this piggy-backing on AMD code and thus turning AMD code into
an unmaintainable mess.
Thx.
--
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquette
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH 1/5] pci_ids: Add Hygon Family 0x18 DF device IDs
2026-04-02 17:56 ` Mario Limonciello
@ 2026-04-03 3:39 ` Lin Wang
0 siblings, 0 replies; 12+ messages in thread
From: Lin Wang @ 2026-04-03 3:39 UTC (permalink / raw)
To: Mario Limonciello, yazen.ghannam, Borislav Petkov
Cc: tglx, mingo, x86, linux-kernel
Hi Mario,
On 4/3/2026 1:56 AM, Mario Limonciello wrote:
> I don't think this is a good idea to start precedent of using exact same
> define for Hygon and AMD because it might not always be true.
>
> Could we instead have something like
>
> #define PCI_DEVICE_ID_HYGON_17H_M30H_DF_F3 PCI_DEVICE_ID_AMD_17H_M30H_DF_F3
>
> And then anywhere you're using it like you describe (such as how I see
> in patch 3) then you can use PCI_DEVICE_ID_HYGON_17H_M30H_DF_F3 instead.
Agreed, thank you for the clear guidance.
In v2, all Hygon device IDs will be defined with explicit values in the
Hygon section of pci_ids.h, with no reference to any AMD defines.
Specifically:
- The two AMD F4 IDs added in patch 1 (PCI_DEVICE_ID_AMD_17H_DF_F4 and
PCI_DEVICE_ID_AMD_17H_M30H_DF_F4) will be removed; they were added
solely because Hygon code needed those values.
- Four new Hygon defines will replace the AMD-prefixed IDs currently
used in patch 3's PCI_DEVICE() tables:
PCI_DEVICE_ID_HYGON_18H_M04H_DF_F3 0x1463
PCI_DEVICE_ID_HYGON_18H_M04H_DF_F3B 0x1493
PCI_DEVICE_ID_HYGON_18H_M04H_DF_F4 0x1464
PCI_DEVICE_ID_HYGON_18H_M04H_DF_F4B 0x1494
Thx & Br,
Lin
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH 3/5] x86/hygon: Add DF misc-based node enumeration helpers
2026-04-02 18:50 ` Mario Limonciello
@ 2026-04-03 5:15 ` Lin Wang
0 siblings, 0 replies; 12+ messages in thread
From: Lin Wang @ 2026-04-03 5:15 UTC (permalink / raw)
To: Mario Limonciello, yazen.ghannam, Borislav Petkov
Cc: tglx, mingo, x86, linux-kernel
Hi Mario,
On 4/3/2026 2:50 AM, Mario Limonciello wrote:
>> Provide a small set of Hygon-only helpers that can be consumed by
>> amd_nb/amd_node code without extending AMD-only structs.
>>
>> Signed-off-by: Lin Wang <wanglin@open-hieco.net>
>> ---
>> arch/x86/kernel/Makefile | 2 +-
>> arch/x86/kernel/hygon_node.c | 721 +++++++++++++++++++++++++++++++++++
>
> A lot of helpers just for Hygon - do we perhaps want CONFIG_HYGON_NODE?
Good suggestion. In v2 we will introduce CONFIG_HYGON_NODE to gate the
Hygon-specific code. Something along the lines of:
config HYGON_NODE
bool "Hygon Family 0x18 Data Fabric node support"
depends on AMD_NODE
...
The exact structure of the Kconfig and how Hygon code integrates with
the existing AMD infrastructure is something we are revisiting based on
the broader feedback on this series. The goal is to keep Hygon
enablement self-contained rather than modifying AMD code paths directly.
Thx & Br,
Lin
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH 5/5] x86/amd_node: Support Hygon SMN roots by socket
2026-04-02 23:50 ` Borislav Petkov
@ 2026-04-03 5:29 ` Lin Wang
0 siblings, 0 replies; 12+ messages in thread
From: Lin Wang @ 2026-04-03 5:29 UTC (permalink / raw)
To: Borislav Petkov
Cc: yazen.ghannam, mario.limonciello, tglx, mingo, x86, linux-kernel
Hi Boris,
On 4/3/2026 7:50 AM, Borislav Petkov wrote:
>
> All Hygon enablement goes into Hygon-specific files. If there's a piece of
> functionality which is generic enough, then that can be extracted into
> a function which can be called by both vendors code.
>
> But pls forget this piggy-backing on AMD code and thus turning AMD code into
> an unmaintainable mess.
>
> Thx.
>
Thank you for the clear direction -- this is exactly the kind of
architectural guidance we needed at this stage.
Keeping Hygon enablement out of AMD code paths is the right call. We
will rethink the integration approach for v2 with that principle in mind.
Thx & br,
Lin
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-04-03 5:29 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-04-02 11:15 [RFC PATCH 00/05] x86/hygon: Add Family 0x18 node enumeration and SMN access Lin Wang
2026-04-02 11:15 ` [RFC PATCH 1/5] pci_ids: Add Hygon Family 0x18 DF device IDs Lin Wang
2026-04-02 17:56 ` Mario Limonciello
2026-04-03 3:39 ` Lin Wang
2026-04-02 11:15 ` [RFC PATCH 2/5] x86/hygon: Add Family 0x18 node enumeration API header Lin Wang
2026-04-02 11:15 ` [RFC PATCH 3/5] x86/hygon: Add DF misc-based node enumeration helpers Lin Wang
2026-04-02 18:50 ` Mario Limonciello
2026-04-03 5:15 ` Lin Wang
2026-04-02 11:15 ` [RFC PATCH 4/5] x86/amd_nb: Use Hygon DF misc enumeration for Family 0x18 Lin Wang
2026-04-02 11:15 ` [RFC PATCH 5/5] x86/amd_node: Support Hygon SMN roots by socket Lin Wang
2026-04-02 23:50 ` Borislav Petkov
2026-04-03 5:29 ` Lin Wang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®