From nobody Sun Jun 14 11:29:23 2026 Received: from out28-52.mail.aliyun.com (out28-52.mail.aliyun.com [115.124.28.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 886A0361DDA for ; Thu, 2 Apr 2026 11:15:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775128535; cv=none; b=rQhwop2PakFRUjSAH1M/o9K4fiI/eQCn19rqJuAIGNCIxiDF8H4w1U2d8j/LiwZXxrwEdESfd9H/KOpk0BlgXAx50UhlCj9WH7C1ch3DBfUuWvcT4MCdAD/w705ozYGlcaoglUsn5nG2TsLxP0zDF8iIrCG//drjO1UJXIrJn/Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775128535; c=relaxed/simple; bh=SKTyx0WN8iQscevLOjR+/eBlNlL//RsGFWwv5EhR4O8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=CNRkJ8MAIE97d/0kD33rgYjXpMVYghs7Ok0GLN0JAKH0vHs2P22I/nWM8tO9V27KmF3989GKh2jCsaJM+xuOOOh6JyGuWc4IECvvvAwUWOPYjlrRCQ5hlKahZMeLU61HDoIYPRcI3Nw7oK4f/mQmetf1uQMD8Td0sH4/9AWG8Dg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=open-hieco.net; spf=pass smtp.mailfrom=open-hieco.net; arc=none smtp.client-ip=115.124.28.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=open-hieco.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=open-hieco.net X-Alimail-AntiSpam: AC=CONTINUE;BC=0.2437944|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_system_inform|0.1887-0.00142996-0.80987;FP=13390094307819070975|1|1|1|0|-1|-1|-1;HT=maildocker-contentspam033040074035;MF=wanglin@open-hieco.net;NM=1;PH=DS;RN=7;RT=7;SR=0;TI=SMTPD_---.h3l6NLe_1775128521; Received: from localhost.localdomain(mailfrom:wanglin@open-hieco.net fp:SMTPD_---.h3l6NLe_1775128521 cluster:ay29) by smtp.aliyun-inc.com; Thu, 02 Apr 2026 19:15:21 +0800 From: Lin Wang To: yazen.ghannam@amd.com, mario.limonciello@amd.com, Borislav Petkov Cc: tglx@kernel.org, mingo@redhat.com, x86@kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 1/5] pci_ids: Add Hygon Family 0x18 DF device IDs Date: Thu, 2 Apr 2026 19:15:07 +0800 Message-ID: <20260402111515.1155505-2-wanglin@open-hieco.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260402111515.1155505-1-wanglin@open-hieco.net> References: <20260402111515.1155505-1-wanglin@open-hieco.net> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- 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 =20 #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 =20 #define PCI_VENDOR_ID_META 0x1d9b =20 --=20 2.43.0 From nobody Sun Jun 14 11:29:23 2026 Received: from out28-1.mail.aliyun.com (out28-1.mail.aliyun.com [115.124.28.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D22103CD8B7 for ; Thu, 2 Apr 2026 11:46:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.1 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775130387; cv=none; b=kvORo1wNPzyyMKE/6hmp26e/4nSyGG4sReIyVpd/uaEbUJJY9IzFexqzPWBf/U3SnZXDoGZzUapobsdFUyQ8AYXruz+m/37mCw7KTkg/W/BGF2a9JjzgmgdKXvc8Q7Z9ysQtfI5wIDrLkKBiG8wCj6Xpn4QEFYw3TKmM/nPgWww= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775130387; c=relaxed/simple; bh=xN8NYqeUsIVA4kWlAGW+Ad/RBitFsvBnQPdXPygvNi0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GDF/+JW+hvokO4mUIkenbGdsUUt1Z2qO8tE3dDKqIuqTg/ArK9Yfqp73SAmWyZz0XnPMk/cxJm8vDrjL3QBuC9MFnch+yd8QZakUuAapTLZAEddlq0yaW0rv1yP4+ievPHfm/B1nae88eTsQ7O7FxXF0tjay+0UTFkbhW1a/ht0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=open-hieco.net; spf=pass smtp.mailfrom=open-hieco.net; arc=none smtp.client-ip=115.124.28.1 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=open-hieco.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=open-hieco.net X-Alimail-AntiSpam: AC=CONTINUE;BC=0.07436299|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_alarm|0.0022019-0.000965298-0.996833;FP=5197674443380967571|3|1|1|0|-1|-1|-1;HT=maildocker-contentspam033037032089;MF=wanglin@open-hieco.net;NM=1;PH=DS;RN=7;RT=7;SR=0;TI=SMTPD_---.h3l6NMG_1775128522; Received: from localhost.localdomain(mailfrom:wanglin@open-hieco.net fp:SMTPD_---.h3l6NMG_1775128522 cluster:ay29) by smtp.aliyun-inc.com; Thu, 02 Apr 2026 19:15:22 +0800 From: Lin Wang To: yazen.ghannam@amd.com, mario.limonciello@amd.com, Borislav Petkov Cc: tglx@kernel.org, mingo@redhat.com, x86@kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 2/5] x86/hygon: Add Family 0x18 node enumeration API header Date: Thu, 2 Apr 2026 19:15:08 +0800 Message-ID: <20260402111515.1155505-3-wanglin@open-hieco.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260402111515.1155505-1-wanglin@open-hieco.net> References: <20260402111515.1155505-1-wanglin@open-hieco.net> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- 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 =20 HYGON PROCESSOR SUPPORT M: Pu Wen +M: Lin Wang 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 =20 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 +#include + +#include +#include + +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 th= an + * 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 >=3D 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 =3D hygon_f18h_model(); + + return m >=3D 0x4 && m <=3D 0xf; +} + +/** + * hygon_f18h_m10h() - check Hygon family 18h model 10h..1fh + */ +static inline bool hygon_f18h_m10h(void) +{ + u8 m =3D hygon_f18h_model(); + + return m >=3D 0x10; +} + + +#endif /* _ASM_X86_HYGON_NODE_H */ --=20 2.43.0 From nobody Sun Jun 14 11:29:23 2026 Received: from out198-2.us.a.mail.aliyun.com (out198-2.us.a.mail.aliyun.com [47.90.198.2]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 11BC23C5DD6 for ; Thu, 2 Apr 2026 11:15:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=47.90.198.2 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775128545; cv=none; b=YPLRRG9q3+VDwVf2n1aCzrNP9Lba2+GTNvYVn/YfYUfuTCc1Kj/w3IGLmI7abNFVOUM9BpW3WPiatbZLnu2wV92DARKhKCuonKSoVYJLkELN9Zy17u3ZZNa1U0l6xferlZfHS7zlbD1S994WDgT1vU9rWjO4nq7kFwnnWIWOcSE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775128545; c=relaxed/simple; bh=av67m4TqZrQKCocXNJOMiRVuFmYdTq1Sm/uBtq4udio=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JdUZManJ6V67qEz1QIqPcrnK+GstHgy1+Ipz4gzHoXNtqaftrLNgbC9s2+64750Mj+zmMJu7p/O9Xqz2KEFXJcnLAvjx/kU3Wv+ilNZELyEBmYZPNWGB085aikTm17M1EdINZjWJjiofS+OTCHz9WxZJkqSyXrMY+vhPi94ZgpU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=open-hieco.net; spf=pass smtp.mailfrom=open-hieco.net; arc=none smtp.client-ip=47.90.198.2 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=open-hieco.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=open-hieco.net X-Alimail-AntiSpam: AC=CONTINUE;BC=0.07436259|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_alarm|0.005041-0.00103967-0.993919;FP=15141520028457593087|3|1|1|0|-1|-1|-1;HT=maildocker-contentspam033037025160;MF=wanglin@open-hieco.net;NM=1;PH=DS;RN=7;RT=7;SR=0;TI=SMTPD_---.h3l6NN0_1775128522; Received: from localhost.localdomain(mailfrom:wanglin@open-hieco.net fp:SMTPD_---.h3l6NN0_1775128522 cluster:ay29) by smtp.aliyun-inc.com; Thu, 02 Apr 2026 19:15:23 +0800 From: Lin Wang To: yazen.ghannam@amd.com, mario.limonciello@amd.com, Borislav Petkov Cc: tglx@kernel.org, mingo@redhat.com, x86@kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 3/5] x86/hygon: Add DF misc-based node enumeration helpers Date: Thu, 2 Apr 2026 19:15:09 +0800 Message-ID: <20260402111515.1155505-4-wanglin@open-hieco.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260402111515.1155505-1-wanglin@open-hieco.net> References: <20260402111515.1155505-1-wanglin@open-hieco.net> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 >=3D 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 --- 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) +=3D early_printk.o obj-$(CONFIG_HPET_TIMER) +=3D hpet.o =20 obj-$(CONFIG_AMD_NB) +=3D amd_nb.o -obj-$(CONFIG_AMD_NODE) +=3D amd_node.o +obj-$(CONFIG_AMD_NODE) +=3D amd_node.o hygon_node.o obj-$(CONFIG_DEBUG_NMI_SELFTEST) +=3D nmi_selftest.o =20 obj-$(CONFIG_KVM_GUEST) +=3D 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* 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 belo= w) + * + * 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 =3D=3D DFID) + * Model 06h-08h: F5x180[19:16] (MyDieId !=3D DFID, different numbering) + * Model 10h+: F1x200[23:20] (MyDieId =3D=3D 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 >=3D 4 */ +}; + +struct hygon_node_cache { + struct hygon_node *nodes; /* sorted: CDD first, then IOD */ + u16 num_nodes; /* CDD + IOD =3D amd_nb[]/amd_roots[] size */ + u16 num_cdd; /* CDD only =3D 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 =3D 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 vend= or + * ID is the discriminator. Models 05h-08h and 10h+ have Hygon-specific I= Ds. + */ +static const struct pci_device_id hygon_nb_misc_ids[] =3D { + { 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[] =3D { + { 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 mix= ed + * silicon, which is handled separately in hygon_read_df_reg(). + */ +static const struct hygon_df_func_ids hygon_df_table[] =3D { + { 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 ch= ar *phase) +{ + u16 i; + + pr_debug("%s: %u nodes\n", phase, cache->num_nodes); + + for (i =3D 0; i < cache->num_nodes; i++) { + const struct hygon_node *node =3D &cache->nodes[i]; + + pr_debug("%s: node[%u] %04x:%02x:%02x.%u socket=3D%u dfid=3D%u type=3D%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 referen= ce + * 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 =3D 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 =3D 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 =3D boot_cpu_data.x86_model; + + for (entry =3D hygon_df_table; entry->f1_id; entry++) { + if (model >=3D entry->model_start && model <=3D entry->model_end) + return entry; + } + + pr_warn_once("unsupported Hygon Fam18h model 0x%x, Hygon node support dis= abled\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 =3D hygon_get_df_func_ids(); + if (!ids) + return -ENODEV; + + if (func =3D=3D HYGON_DF_F1) { + expected_device =3D 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 =3D=3D 0x5 && + misc->device !=3D PCI_DEVICE_ID_HYGON_18H_M05H_DF_F3) + expected_device =3D PCI_DEVICE_ID_HYGON_18H_M04H_DF_F1; + } else if (func =3D=3D HYGON_DF_F5) { + expected_device =3D ids->f5_id; + } else { + return -EINVAL; + } + + if (!expected_device) + return -ENODEV; + + sibling =3D 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 !=3D PCI_VENDOR_ID_HYGON || + sibling->device !=3D expected_device) { + pci_dev_put(sibling); + return -ENODEV; + } + + err =3D 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 =3D hygon_read_df_reg(misc, HYGON_DF_F1, DF_F1_SYSTEM_CFG, ®); + if (ret) + return ret; + + id->socket_id =3D (reg >> 28) & 0x7; + id->dfid =3D (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 >=3D 0x6 && + boot_cpu_data.x86_model <=3D 0x8) { + ret =3D hygon_read_df_reg(misc, HYGON_DF_F5, DF_F5_FABRIC_ID, ®); + if (ret) + return ret; + id->dfid =3D (reg >> 16) & 0xf; + } + + return 0; +} + +static void hygon_release_nodes(struct hygon_node *nodes, u16 count) +{ + u16 i; + + for (i =3D 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 =3D 0, count =3D 0; + u8 socket_mask =3D 0; + int ret; + + misc =3D NULL; + while ((misc =3D next_hygon_dev(misc, hygon_nb_misc_ids))) + capacity++; + + if (!capacity) + return -ENODEV; + + nodes =3D kcalloc(capacity, sizeof(*nodes), GFP_KERNEL); + if (!nodes) + return -ENOMEM; + + misc =3D NULL; + while ((misc =3D next_hygon_dev(misc, hygon_nb_misc_ids))) { + struct hygon_df_id id; + struct pci_dev *link; + + link =3D hygon_get_link(misc); + if (!link) { + pci_dev_put(misc); + ret =3D -ENODEV; + goto fail; + } + + ret =3D hygon_read_df_id(misc, &id); + if (ret) { + pci_dev_put(link); + pci_dev_put(misc); + goto fail; + } + + if (count >=3D capacity) { + pci_dev_put(link); + pci_dev_put(misc); + ret =3D -ENODEV; + goto fail; + } + + pr_debug("collect: %04x:%02x:%02x.%u socket=3D%u dfid=3D%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 =3D pci_dev_get(misc); + nodes[count].link =3D link; + nodes[count].socket_id =3D id.socket_id; + nodes[count].dfid =3D id.dfid; + nodes[count].is_cdd =3D (id.dfid >=3D 4); + socket_mask |=3D BIT(id.socket_id); + count++; + } + + if (!count) { + ret =3D -ENODEV; + goto fail; + } + + cache->nodes =3D nodes; + cache->num_nodes =3D count; + cache->num_sockets =3D hweight8(socket_mask); + + if (!cache->num_sockets || + socket_mask !=3D GENMASK(cache->num_sockets - 1, 0)) { + pr_warn("sparse socket IDs not supported (mask=3D0x%x)\n", + socket_mask); + ret =3D -EINVAL; + goto fail; + } + + return 0; + +fail: + hygon_release_nodes(nodes, count); + cache->nodes =3D NULL; + cache->num_nodes =3D 0; + return ret; +} + +/* Sort CDD nodes before IOD nodes, then order by hardware (socket_id, dfi= d). */ +static int hygon_node_cmp(const void *a, const void *b) +{ + const struct hygon_node *left =3D a; + const struct hygon_node *right =3D b; + + if (left->is_cdd !=3D right->is_cdd) + return right->is_cdd - left->is_cdd; + + if (left->socket_id !=3D 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 co= mpute + * 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] =3D { 0 }; + + hygon_log_nodes(cache, "before-sort"); + + sort(cache->nodes, cache->num_nodes, sizeof(*cache->nodes), + hygon_node_cmp, NULL); + + for (i =3D 0; i < cache->num_nodes; i++) { + if (!cache->nodes[i].is_cdd) + break; + } + + cache->num_cdd =3D 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 =3D cache->num_cdd / cache->num_sockets; + if (!cdd_per_socket) + return -EINVAL; + + for (i =3D 0; i < cache->num_cdd; i++) { + u8 socket_id =3D cache->nodes[i].socket_id; + + if (socket_id >=3D cache->num_sockets) + return -EINVAL; + per_sock_count[socket_id]++; + } + + for (i =3D 0; i < cache->num_sockets; i++) { + if (per_sock_count[i] !=3D 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]] = =3D 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 ascendi= ng + * DFID order corresponds to ascending phys_nid orde= r. + * + * A local bitmap (nid_seen) de-duplicates CPUs that share a node; each + * unique phys_nid is collected exactly once into phys_nids[]. The resulti= ng + * 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) =3D { }; + u8 *phys_nids; + u16 count =3D 0; + u16 i; + int cpu; + + phys_nids =3D kcalloc(cache->num_cdd, sizeof(*phys_nids), GFP_KERNEL); + if (!phys_nids) + return -ENOMEM; + + for_each_online_cpu(cpu) { + unsigned int phys_nid =3D topology_amd_node_id(cpu); + + if (phys_nid >=3D 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 >=3D cache->num_cdd) { + pr_warn("more unique phys_node_ids than CDD nodes\n"); + kfree(phys_nids); + return -EINVAL; + } + + phys_nids[count++] =3D (u8)phys_nid; + } + + if (count !=3D 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 =3D 0; i < count; i++) + cache->nid_to_logical[phys_nids[i]] =3D 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 =3D NULL; + cache->num_nodes =3D 0; + cache->num_cdd =3D 0; + cache->num_sockets =3D 0; + memset(cache->nid_to_logical, HYGON_NID_INVALID, + sizeof(cache->nid_to_logical)); + cache->ready =3D 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 bu= ild. + * 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 !=3D X86_VENDOR_HYGON || + boot_cpu_data.x86 !=3D 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 =3D hygon_collect_nodes(&tmp); + if (err) + goto fail; + + err =3D hygon_sort_and_classify(&tmp); + if (err) + goto fail; + + err =3D hygon_build_nid_map(&tmp); + if (err) + goto fail; + + hygon_cache =3D tmp; + /* Pairs with smp_load_acquire() above; ensures cache is visible to all C= PUs. */ + 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 =3D=3D X86_VENDOR_HYGON && + boot_cpu_data.x86 =3D=3D 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 =3D hygon_read_df_id(misc, &id); + if (ret) + return ret; + + *dfid =3D 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 >=3D 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 >=3D 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 =3D topology_amd_node_id(cpu); + if (phys_nid >=3D HYGON_MAX_PHYS_NID) + return -ENODEV; + + logical_id =3D hygon_cache.nid_to_logical[phys_nid]; + return logical_id =3D=3D HYGON_NID_INVALID ? -ENODEV : logical_id; +} +EXPORT_SYMBOL_GPL(hygon_cpu_to_logical_node); --=20 2.43.0 From nobody Sun Jun 14 11:29:23 2026 Received: from out28-148.mail.aliyun.com (out28-148.mail.aliyun.com [115.124.28.148]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 72ACB3B894D for ; Thu, 2 Apr 2026 11:15:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.148 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775128538; cv=none; b=dYkk5r1eldsscnEfFZ72tFfQsZIviCiX9ceGP1+kr4IkFcz1iHI8n47V5csbpugc5uKuZjVqOB8Fog3P+9GLvXhsgRhV5hdw4KsLMMp72tVtBmwD8nw0LtXMmRzGUFnIBhwam8K04ujKP+7PV7SVl82upeAFAqnPZ4bBTuGprGg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775128538; c=relaxed/simple; bh=6BXrWPNxTIFz1q70w1REcws1uZcYrY3O5DdNDLeeIbE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lvMDwStcwcii8ThfWG3oxQseEMGfq4oxAe9bSWXzMGJRoqs+ppZP8gxBVLcHfnRig64T5jFc6ICD13eNVOpzGSXNgYVsVeUPFcpDEImh0tpPl837VJHMfHwvQb3bFq8QJLs+iO5IyLJO9SAqxMqyYTpLokRLNlLjPDwxRwYFYo0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=open-hieco.net; spf=pass smtp.mailfrom=open-hieco.net; arc=none smtp.client-ip=115.124.28.148 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=open-hieco.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=open-hieco.net X-Alimail-AntiSpam: AC=CONTINUE;BC=0.09512658|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_alarm|0.00552744-0.00264194-0.991831;FP=7316986052935765195|3|1|1|0|-1|-1|-1;HT=maildocker-contentspam033037017159;MF=wanglin@open-hieco.net;NM=1;PH=DS;RN=7;RT=7;SR=0;TI=SMTPD_---.h3l6NNd_1775128523; Received: from localhost.localdomain(mailfrom:wanglin@open-hieco.net fp:SMTPD_---.h3l6NNd_1775128523 cluster:ay29) by smtp.aliyun-inc.com; Thu, 02 Apr 2026 19:15:23 +0800 From: Lin Wang To: yazen.ghannam@amd.com, mario.limonciello@amd.com, Borislav Petkov Cc: tglx@kernel.org, mingo@redhat.com, x86@kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 4/5] x86/amd_nb: Use Hygon DF misc enumeration for Family 0x18 Date: Thu, 2 Apr 2026 19:15:10 +0800 Message-ID: <20260402111515.1155505-5-wanglin@open-hieco.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260402111515.1155505-1-wanglin@open-hieco.net> References: <20260402111515.1155505-1-wanglin@open-hieco.net> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- 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 @@ =20 #include #include +#include +#include =20 static u32 *flush_words; =20 @@ -60,13 +62,20 @@ EXPORT_SYMBOL_GPL(node_to_amd_nb); =20 static int amd_cache_northbridges(void) { + bool is_hygon =3D (boot_cpu_data.x86_vendor =3D=3D X86_VENDOR_HYGON); struct amd_northbridge *nb; u16 i; =20 if (amd_northbridges.num) return 0; =20 - amd_northbridges.num =3D amd_num_nodes(); + if (is_hygon) { + amd_northbridges.num =3D hygon_node_num(); + if (!amd_northbridges.num) + return -ENODEV; + } else { + amd_northbridges.num =3D amd_num_nodes(); + } =20 nb =3D kzalloc_objs(struct amd_northbridge, amd_northbridges.num); if (!nb) @@ -75,7 +84,8 @@ static int amd_cache_northbridges(void) amd_northbridges.nb =3D nb; =20 for (i =3D 0; i < amd_northbridges.num; i++) { - node_to_amd_nb(i)->misc =3D amd_node_get_func(i, 3); + node_to_amd_nb(i)->misc =3D is_hygon ? hygon_node_get_func(i, 3) + : amd_node_get_func(i, 3); =20 /* * Each Northbridge must have a 'misc' device. @@ -87,7 +97,8 @@ static int amd_cache_northbridges(void) return -ENODEV; } =20 - node_to_amd_nb(i)->link =3D amd_node_get_func(i, 4); + node_to_amd_nb(i)->link =3D is_hygon ? hygon_node_get_func(i, 4) + : amd_node_get_func(i, 4); } =20 if (amd_gart_present()) --=20 2.43.0 From nobody Sun Jun 14 11:29:23 2026 Received: from out28-145.mail.aliyun.com (out28-145.mail.aliyun.com [115.124.28.145]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7E8F02E1746 for ; Thu, 2 Apr 2026 11:15:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.145 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775128535; cv=none; b=BPGjf9bvtNzn4TJrLirAd6jaJRLeiPiYfsVkjdbh5W10VO++Rbk4QhN2wiGZLUM5MFH/7aqG0FITg3yA5qPIhHN4ekFJXu620s0kRSreACkbHROBWIhdMb4WpuWJ/GsNiOQ0iSlkSPntJL0XD+P7ibkb3QFy98k1Ntk8I3ftKoE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775128535; c=relaxed/simple; bh=sCQ4c+jetcyaVJ4Aqqa5GZhxx7w7i73rnP/Kf0ZbkYM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=gy7STGK7zn4Z5OEUYgeiU36CAc3L5LSlSas0lczeNLd8XREFxE1M0Y50Y+0NUeNktP5bmNTnXGebw87heXFdb5AylppadznR8jc+n6z40rWs2HFIOAvUDr5bP6urXeGeXiIRRcA0F13z8qrdROQpnncTRQuhoKmXKYy6Cr67hGg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=open-hieco.net; spf=pass smtp.mailfrom=open-hieco.net; arc=none smtp.client-ip=115.124.28.145 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=open-hieco.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=open-hieco.net X-Alimail-AntiSpam: AC=CONTINUE;BC=0.07436259|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_alarm|0.00571223-0.00209798-0.99219;FP=13839535400029864074|3|1|1|0|-1|-1|-1;HT=maildocker-contentspam033032027189;MF=wanglin@open-hieco.net;NM=1;PH=DS;RN=7;RT=7;SR=0;TI=SMTPD_---.h3l6NOA_1775128524; Received: from localhost.localdomain(mailfrom:wanglin@open-hieco.net fp:SMTPD_---.h3l6NOA_1775128524 cluster:ay29) by smtp.aliyun-inc.com; Thu, 02 Apr 2026 19:15:24 +0800 From: Lin Wang To: yazen.ghannam@amd.com, mario.limonciello@amd.com, Borislav Petkov Cc: tglx@kernel.org, mingo@redhat.com, x86@kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 5/5] x86/amd_node: Support Hygon SMN roots by socket Date: Thu, 2 Apr 2026 19:15:11 +0800 Message-ID: <20260402111515.1155505-6-wanglin@open-hieco.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260402111515.1155505-1-wanglin@open-hieco.net> References: <20260402111515.1155505-1-wanglin@open-hieco.net> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- 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 @@ */ =20 #include +#include #include +#include =20 /* * 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) } =20 static struct pci_dev **amd_roots; +static u16 smn_num_nodes; =20 /* 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 =3D -ENODEV; =20 - if (node >=3D amd_num_nodes()) + if (node >=3D smn_num_nodes) return err; =20 root =3D amd_roots[node]; @@ -151,7 +154,7 @@ static ssize_t smn_node_write(struct file *file, const = char __user *userbuf, if (ret) return ret; =20 - if (node >=3D amd_num_nodes()) + if (node >=3D smn_num_nodes) return -ENODEV; =20 debug_node =3D node; @@ -246,10 +249,14 @@ __setup("amd_smn_debugfs_enable", amd_smn_enable_dfs); =20 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] =3D { }; + 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; =20 - if (!cpu_feature_enabled(X86_FEATURE_ZEN)) + if (!cpu_feature_enabled(X86_FEATURE_ZEN) && + boot_cpu_data.x86_vendor !=3D X86_VENDOR_HYGON) return 0; =20 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, NU= LL)) { + 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++; } =20 - pr_debug("Found %d AMD root devices\n", num_roots); + pr_debug("Found %d SMN root devices\n", num_roots); =20 if (!num_roots) return -ENODEV; =20 - num_nodes =3D amd_num_nodes(); - amd_roots =3D kzalloc_objs(*amd_roots, num_nodes); - if (!amd_roots) - return -ENOMEM; + if (boot_cpu_data.x86_vendor =3D=3D 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 =3D 0; + + num_nodes =3D hygon_node_num(); + if (!num_nodes) + return -ENODEV; =20 - roots_per_node =3D num_roots / num_nodes; + num_sockets =3D hygon_socket_num(); + if (!num_sockets) + return -ENODEV; =20 - count =3D 0; - node =3D 0; - root =3D NULL; - while (node < num_nodes && (root =3D 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; =20 - pci_dbg(root, "is root for AMD node %u\n", node); - amd_roots[node++] =3D 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 =3D num_nodes; + amd_roots =3D kzalloc_objs(*amd_roots, num_nodes); + if (!amd_roots) + return -ENOMEM; + + roots_per_socket =3D num_roots / num_sockets; + count =3D 0; + root =3D NULL; + while (socket < num_sockets && (root =3D get_next_root(root))) { + if (count++ % roots_per_socket) + continue; + + pci_dbg(root, "is root for Hygon socket %u\n", socket); + socket_roots[socket++] =3D root; + } + + if (socket !=3D num_sockets) { + ret =3D -ENODEV; + goto err_free; + } + + for (node =3D 0; node < num_nodes; node++) { + socket_id =3D hygon_node_socket(node); + + if (socket_id >=3D num_sockets) { + ret =3D -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] =3D socket_roots[socket_id]; + } + + } else { + num_nodes =3D amd_num_nodes(); + smn_num_nodes =3D num_nodes; + amd_roots =3D kzalloc_objs(*amd_roots, num_nodes); + if (!amd_roots) + return -ENOMEM; + + roots_per_node =3D num_roots / num_nodes; + + count =3D 0; + node =3D 0; + root =3D NULL; + while (node < num_nodes && (root =3D 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++] =3D root; + } } =20 + if (enable_dfs) { debugfs_dir =3D debugfs_create_dir("amd_smn", arch_debugfs_dir); =20 @@ -311,6 +387,12 @@ static int __init amd_smn_init(void) smn_exclusive =3D true; =20 return 0; + +err_free: + kfree(amd_roots); + amd_roots =3D NULL; + smn_num_nodes =3D 0; + return ret; } =20 fs_initcall(amd_smn_init); --=20 2.43.0