[RFC PATCH 00/05] x86/hygon: Add Family 0x18 node enumeration and SMN access

Lin Wang posted 5 patches 2 months, 1 week ago
There is a newer version of this series
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
[RFC PATCH 00/05] x86/hygon: Add Family 0x18 node enumeration and SMN access
Posted by Lin Wang 2 months, 1 week ago
== 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