mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Mario Limonciello <mario.limonciello@amd.com>
To: Lin Wang <wanglin@open-hieco.net>,
	yazen.ghannam@amd.com, Borislav Petkov <bp@alien8.de>
Cc: tglx@kernel.org, mingo@redhat.com, x86@kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [RFC PATCH 3/5] x86/hygon: Add DF misc-based node enumeration helpers
Date: Thu, 2 Apr 2026 13:50:37 -0500	[thread overview]
Message-ID: <e6f2dc03-35c5-46fe-ab3a-c23af11133b2@amd.com> (raw)
In-Reply-To: <20260402111515.1155505-4-wanglin@open-hieco.net>

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, &reg);
> +       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, &reg);
> +               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
> 


  reply	other threads:[~2026-04-02 18:50 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=e6f2dc03-35c5-46fe-ab3a-c23af11133b2@amd.com \
    --to=mario.limonciello@amd.com \
    --cc=bp@alien8.de \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mingo@redhat.com \
    --cc=tglx@kernel.org \
    --cc=wanglin@open-hieco.net \
    --cc=x86@kernel.org \
    --cc=yazen.ghannam@amd.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is 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®