From nobody Sat Sep 26 13:09:27 2026 Received: from out28-4.mail.aliyun.com (out28-4.mail.aliyun.com [115.124.28.4]) (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 34E30455606; Tue, 1 Sep 2026 07:17:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788247060; cv=none; b=dvhpU3Y/G1VNOW2Ms1OfTNlhNSJ5YSawqDSZasUM3+ahGgGkoZIheJtPn4bB4HJI97YfREa6SrDE/rdO1cVoFzZcfwFKk7yj0heY2w3PFYuS01R+g586OyPZoefmhfflnx+P5ZxqFMRIwqgszKZYD2CtefcxlqPk92V36dLyje8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788247060; c=relaxed/simple; bh=bzYTH4+M3T/zklhRcUkacMj2cPGw/98t9zQ04BhEHBM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Pmu0+ttkn7dND/VCOWjFcF1nxauNe7y05UJf2QDDHhQx/2HXLQR6cWFrWHiJSMDq+1cH3C6UBOGd6wR6n59RjWZVLO5gzNf37UZXjKtolWfRkLTlpbshqQDbfRFbVS/4pxiysBllhfnHalR4ApQNk2T1pI0eEpo/sOwr7TgxIuo= 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.4 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.1416918|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_system_inform|0.0639844-0.000926813-0.935089;FP=18001774820116282811|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033037031241;MF=wanglin@open-hieco.net;NM=1;PH=DS;RN=11;RT=11;SR=0;TI=SMTPD_---.j2BhS3u_1788247047; Received: from localhost.localdomain(mailfrom:wanglin@open-hieco.net fp:SMTPD_---.j2BhS3u_1788247047 cluster:ay29) by smtp.aliyun-inc.com; Tue, 01 Sep 2026 15:17:27 +0800 From: Lin Wang To: Borislav Petkov , yazen.ghannam@amd.com, mario.limonciello@amd.com Cc: tglx@linutronix.de, mingo@redhat.com, dave.hansen@linux.intel.com, "H. Peter Anvin" , bhelgaas@google.com, x86@kernel.org, linux-pci@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 1/5] pci_ids: Add Hygon Family 0x18 DF F3/F4 device IDs Date: Tue, 1 Sep 2026 15:16:56 +0800 Message-ID: <20260901071700.3255382-2-wanglin@open-hieco.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260901071700.3255382-1-wanglin@open-hieco.net> References: <20260901071700.3255382-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 node discovery identifies Data Fabric misc (F3) and link (F4) functions by PCI ID rather than a fixed PCI slot. Models 0x04 use 0x1463/0x1464, with 0x1493/0x1494 on the mixed-silicon variant. Models 0x05 through 0x08 use 0x14b3/0x14b4. Some IDs have the same numeric values as AMD DF devices. PCI identity also includes the vendor, so Hygon-prefixed names keep the two vendors' definitions independent. Signed-off-by: Lin Wang --- include/linux/pci_ids.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 63774ae2bf71..d9f3e01033f9 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2615,6 +2615,12 @@ #define PCI_VENDOR_ID_ROCKCHIP 0x1d87 =20 #define PCI_VENDOR_ID_HYGON 0x1d94 +#define PCI_DEVICE_ID_HYGON_18H_M04H_DF_F3 0x1463 +#define PCI_DEVICE_ID_HYGON_18H_M04H_DF_F4 0x1464 +#define PCI_DEVICE_ID_HYGON_18H_M04H_DF_F3B 0x1493 +#define PCI_DEVICE_ID_HYGON_18H_M04H_DF_F4B 0x1494 +#define PCI_DEVICE_ID_HYGON_18H_M05H_DF_F3 0x14b3 +#define PCI_DEVICE_ID_HYGON_18H_M05H_DF_F4 0x14b4 =20 #define PCI_VENDOR_ID_META 0x1d9b =20 --=20 2.43.0 From nobody Sat Sep 26 13:09:27 2026 Received: from out28-173.mail.aliyun.com (out28-173.mail.aliyun.com [115.124.28.173]) (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 21D43451991; Tue, 1 Sep 2026 07:17:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.173 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788247061; cv=none; b=uKAwshik4H2S1ed7w+B5XnvPqW5lFaEJaXb2h3xjXjaKwpnEMikDHolFSRxPrCBLbD/0dARmLPpoWI3+IRuBeoXB3tkzslGExryttxWz4i6hxI/sHhhstvh8o4v3jYzDzOsu1BqM5e9VVKVnxymr6XKv+KLIkV4OhWIcDVUn8MQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788247061; c=relaxed/simple; bh=r9onqjgG20f4tutW35XUwVBArGdBQEaouCQZRO4oKtg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=m//zqox+2uYAm/9KoMsOyq6aZDWO83sAkL0mdWEWkIZtsKCpjD1mwGPxx1b16Pwncf/lyylgnbwhP201tiOVsJD3KKopfkn7GpecaEg/wdkskGOmQcAbHFZfO+7Grf2bkbKKJOwUWlcNffFJIDkH0DNEJ0bwdhUd/mQi/M/6lnA= 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.173 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.00626469-0.00110123-0.992634;FP=14081485529768805627|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033045213054;MF=wanglin@open-hieco.net;NM=1;PH=DS;RN=11;RT=11;SR=0;TI=SMTPD_---.j2BhS55_1788247048; Received: from localhost.localdomain(mailfrom:wanglin@open-hieco.net fp:SMTPD_---.j2BhS55_1788247048 cluster:ay29) by smtp.aliyun-inc.com; Tue, 01 Sep 2026 15:17:28 +0800 From: Lin Wang To: Borislav Petkov , yazen.ghannam@amd.com, mario.limonciello@amd.com Cc: tglx@linutronix.de, mingo@redhat.com, dave.hansen@linux.intel.com, "H. Peter Anvin" , bhelgaas@google.com, x86@kernel.org, linux-pci@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 2/5] x86/hygon: Add Family 0x18 DF node enumeration Date: Tue, 1 Sep 2026 15:16:57 +0800 Message-ID: <20260901071700.3255382-3-wanglin@open-hieco.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260901071700.3255382-1-wanglin@open-hieco.net> References: <20260901071700.3255382-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" The AMD node code locates function 3 for node N at PCI slot 0x18 + N. Hygon has no equivalent node-index-to-slot mapping: DF F3 devices are discovered by PCI ID and the PCI BDF is only an access point. F1x200 provides the socket ID, SocketPresent mask and, on Models 0x04 and 0x05, the DFID. Models 0x06 through 0x08 provide the DFID in F5x180. The resulting (socket ID, DFID) pair identifies a DF node, while SocketPresent verifies that enumeration covers every populated socket. Order CDDs by socket and DFID, followed by IODs, and assign dense node indices. Expose node counts, identity, F3/F4 lookup and PCI-device lookup through . Signed-off-by: Lin Wang --- MAINTAINERS | 3 + arch/x86/Kconfig | 4 + arch/x86/include/asm/hygon/node.h | 152 +++++++ arch/x86/kernel/Makefile | 1 + arch/x86/kernel/hygon_node.c | 685 ++++++++++++++++++++++++++++++ 5 files changed, 845 insertions(+) create mode 100644 arch/x86/include/asm/hygon/node.h create mode 100644 arch/x86/kernel/hygon_node.c diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..44cab306636e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12138,9 +12138,12 @@ 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/cpu/hygon.c +F: arch/x86/kernel/hygon_node.c =20 HYNIX HI556 SENSOR DRIVER M: Sakari Ailus diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 15fd9ec5ecac..6d72e62d87f7 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -3078,6 +3078,10 @@ config AMD_NODE def_bool y depends on CPU_SUP_AMD && PCI =20 +config HYGON_NODE + def_bool y + depends on CPU_SUP_HYGON && PCI + endmenu =20 menu "Binary Emulations" diff --git a/arch/x86/include/asm/hygon/node.h b/arch/x86/include/asm/hygon= /node.h new file mode 100644 index 000000000000..6e15c133b774 --- /dev/null +++ b/arch/x86/include/asm/hygon/node.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Hygon Family 0x18 Data Fabric node enumeration API + * + * This header declares Hygon Fam18h DF node enumeration and function + * access interfaces. + */ +#ifndef _ASM_X86_HYGON_NODE_H +#define _ASM_X86_HYGON_NODE_H + +#include +#include +#include + +struct pci_dev; + +/* DF function indices supported by hygon_node_get_func(). */ +#define HYGON_DF_F3 3 /* misc */ +#define HYGON_DF_F4 4 /* link */ + +/* + * Hygon Core and DDR Dies (CDDs) start at DFID 4. Each CDD contains + * CPU cores and UMCs. I/O Dies (IODs) occupy DFIDs 0-3. + */ +#define HYGON_CDD_DFID_BASE 4 + +/** + * enum hygon_node_type - DF node type, derived from DFID + * @HYGON_NODE_IOD: I/O die (DFID < HYGON_CDD_DFID_BASE); no UMC. + * @HYGON_NODE_CDD: Core and DDR Die (DFID >=3D HYGON_CDD_DFID_BASE); + * contains CPU cores and UMC controllers. + */ +enum hygon_node_type { + HYGON_NODE_IOD =3D 0, + HYGON_NODE_CDD =3D 1, +}; + +/** + * struct hygon_node_info - identity snapshot for a DF node + * @socket_id: physical socket ID, F1x200[30:28] + * @dfid: Data Fabric ID, model-dependent source + * @type: HYGON_NODE_CDD or HYGON_NODE_IOD + */ +struct hygon_node_info { + u8 socket_id; + u8 dfid; + enum hygon_node_type type; +}; + +#ifdef CONFIG_HYGON_NODE + +/** + * hygon_node_num() - total number of DF nodes (CDD + IOD) + * + * This is the upper bound for every node index in this API. + * + * Return: total node count, or 0 if the cache is not ready or this is + * not a Hygon Fam18h platform. + */ +u16 hygon_node_num(void); + +/** + * hygon_cdd_num() - number of Core and DDR Dies (CDD) + * + * A CDD (DFID >=3D 4) has CPU cores and UMC controllers; an IOD + * (DFID < 4) has neither. CDDs occupy nodes [0, hygon_cdd_num()). + * + * Return: CDD count, or 0 if the cache is not ready. + */ +u16 hygon_cdd_num(void); + +/** + * hygon_node_get_info() - read identity snapshot for a DF node + * @node: DF node index in [0, hygon_node_num()) + * @info: output structure (socket_id, dfid, type) + * + * The identity fields are fixed after enumeration and are returned + * together in one lookup. + * + * Return: 0 on success, -EINVAL if @node is out of range or @info is + * NULL, -ENODEV if the cache is not ready. + */ +int hygon_node_get_info(u16 node, struct hygon_node_info *info); + +/** + * hygon_node_get_func() - get DF function PCI device for a node + * @node: DF node index in [0, hygon_node_num()) + * @func: HYGON_DF_F3 or HYGON_DF_F4 + * + * Return: referenced pci_dev on success. NULL if @node is out of + * range, @func is unsupported, or the cache is not ready. The + * caller must release the reference with pci_dev_put(). + */ +struct pci_dev *hygon_node_get_func(u16 node, u8 func); + +/** + * hygon_pci_dev_to_df_node() - find DF node owning the given PCI device + * @pdev: PCI device on the same domain, bus and slot as one of the DF + * nodes (typically a sibling function of the DF misc device, + * e.g. a UMC channel) + * + * Looks up the DF node whose misc (F3) device shares the same PCI + * domain, bus and slot as @pdev, for drivers that hold a sibling + * function and need the containing node index. + * + * Return: DF node index in [0, hygon_node_num()) on success, -EINVAL + * if @pdev is NULL, or -ENODEV if no matching node is found or the + * cache is not ready. + */ +int hygon_pci_dev_to_df_node(struct pci_dev *pdev); + +#else /* !CONFIG_HYGON_NODE */ + +static inline u16 hygon_node_num(void) +{ + return 0; +} + +static inline u16 hygon_cdd_num(void) +{ + return 0; +} + +static inline int hygon_node_get_info(u16 node, struct hygon_node_info *in= fo) +{ + return -ENODEV; +} + +static inline struct pci_dev *hygon_node_get_func(u16 node, u8 func) +{ + return NULL; +} + +static inline int hygon_pci_dev_to_df_node(struct pci_dev *pdev) +{ + return -ENODEV; +} + +#endif /* CONFIG_HYGON_NODE */ + +/* Inline helpers, available regardless of CONFIG_HYGON_NODE. */ + +/** + * is_hygon_f18h() - true on Hygon Family 0x18 CPUs + */ +static inline bool is_hygon_f18h(void) +{ + return boot_cpu_data.x86_vendor =3D=3D X86_VENDOR_HYGON && + boot_cpu_data.x86 =3D=3D 0x18; +} + +#endif /* _ASM_X86_HYGON_NODE_H */ diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 31f46fd00527..b8430e5e53fb 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -136,6 +136,7 @@ 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_HYGON_NODE) +=3D 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..85835938315b --- /dev/null +++ b/arch/x86/kernel/hygon_node.c @@ -0,0 +1,685 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Hygon Family 0x18 Data Fabric node enumeration + * + * A DF instance exposes sibling PCI functions in one slot. Function 3 + * is the misc device used for enumeration, function 4 is the link + * device, and functions 1 and 5 provide the socket and DF identity. + * + * Enumerate F3 devices by PCI ID, validate their F4 siblings, read each + * node's socket ID and DFID, and validate the enumerated socket set + * against SocketPresent. Sort the nodes into a dense cache with Core + * and DDR Dies (CDDs) before I/O Dies (IODs), then by socket ID and + * DFID. + */ + +#define pr_fmt(fmt) "hygon_node: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Maximum socket count this implementation supports. */ +#define HYGON_MAX_SOCKETS 8 + +/* + * 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 - 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) + */ +#define DF_F1_SYSTEM_CFG 0x200 +#define DF_F5_FABRIC_ID 0x180 + +/* DF function numbers for sibling device access (internal use). */ +#define HYGON_DF_F1 1 /* SystemCfg: socket and die identity */ +#define HYGON_DF_F5 5 /* FabricId: 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_M06H_DF_F5 0x14b5 + +/* + * Cached identity for one DF instance. After sorting, CDDs occupy + * nodes[0..num_cdd-1]. + * + * The PCI BDF is the access point for a DF node, not its identity. + * socket_id and dfid, read from DF registers, together identify the + * node in hardware. + */ +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 total */ + u16 num_cdd; /* CDD only */ + u16 num_sockets; + + /* Set after DF node collection, sorting and validation complete. */ + bool ready; +}; + +struct hygon_df_id { + u8 socket_id; + u8 dfid; +}; + +/* DF sibling device IDs used to read node identity. */ +struct hygon_df_cfg { + u16 f1_id; + u16 f5_id; /* 0 =3D not available */ +}; + +/* DF misc (F3) device IDs for all supported Hygon Family 0x18 models. */ +static const struct pci_device_id hygon_nb_misc_ids[] =3D { + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_M04H_DF_F3) }, + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_M04H_DF_F3B) }, + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_M05H_DF_F3) }, + {} +}; + +/* 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_HYGON_18H_M04H_DF_F4) }, + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_M04H_DF_F4B) }, + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_M05H_DF_F4) }, + {} +}; + +static const struct hygon_df_cfg hygon_m04_df_cfg __initconst =3D { + .f1_id =3D PCI_DEVICE_ID_HYGON_18H_M04H_DF_F1, +}; + +static const struct hygon_df_cfg hygon_m05_df_cfg __initconst =3D { + .f1_id =3D PCI_DEVICE_ID_HYGON_18H_M05H_DF_F1, +}; + +static const struct hygon_df_cfg hygon_m06_m08_df_cfg __initconst =3D { + .f1_id =3D PCI_DEVICE_ID_HYGON_18H_M05H_DF_F1, + .f5_id =3D PCI_DEVICE_ID_HYGON_18H_M06H_DF_F5, +}; + +static const struct x86_cpu_id hygon_df_cpuids[] __initconst =3D { + X86_MATCH_VENDOR_FAM_MODEL(HYGON, 0x18, 0x04, &hygon_m04_df_cfg), + X86_MATCH_VENDOR_FAM_MODEL(HYGON, 0x18, 0x05, &hygon_m05_df_cfg), + X86_MATCH_VENDOR_FAM_MODEL(HYGON, 0x18, 0x06, &hygon_m06_m08_df_cfg), + X86_MATCH_VENDOR_FAM_MODEL(HYGON, 0x18, 0x07, &hygon_m06_m08_df_cfg), + X86_MATCH_VENDOR_FAM_MODEL(HYGON, 0x18, 0x08, &hygon_m06_m08_df_cfg), + {} +}; + +static struct hygon_node_cache hygon_cache; + +static void __init hygon_dump_nodes(const struct hygon_node_cache *cache, + const char *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 + * reference is dropped) and the returned device has an elevated + * reference count. + */ +static struct pci_dev * __init 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 * __init 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; +} + +/* Find the DF configuration for the boot CPU. */ +static const struct hygon_df_cfg * __init hygon_get_df_cfg(void) +{ + const struct x86_cpu_id *id =3D x86_match_cpu(hygon_df_cpuids); + + return id ? (const void *)id->driver_data : 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 __init hygon_read_df_reg(struct pci_dev *misc, u8 func, + int offset, u32 *value) +{ + const struct hygon_df_cfg *cfg; + struct pci_dev *sibling; + u16 expected_device; + int err; + + cfg =3D hygon_get_df_cfg(); + if (!cfg) + return -ENODEV; + + if (func =3D=3D HYGON_DF_F1) { + expected_device =3D cfg->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 cfg->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); + } + + /* Do not parse a PCI error response as DF identity. */ + if (PCI_POSSIBLE_ERROR(*value)) { + pr_warn("error response reading %04x:%02x:%02x.%u offset 0x%x\n", + pci_domain_nr(misc->bus), misc->bus->number, + PCI_SLOT(misc->devfn), func, offset); + return -ENODEV; + } + + return 0; +} + +/* + * 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 obtains the DFID + * from [19:16]. + * + * All DF instances on a Hygon system are the same model, so + * boot_cpu_data.x86_model is representative for all devices. + */ +static int __init hygon_read_df_id(struct pci_dev *misc, + struct hygon_df_id *id) +{ + const struct hygon_df_cfg *cfg =3D hygon_get_df_cfg(); + u32 reg; + int ret; + + if (!cfg) + return -ENODEV; + + 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; + + /* Read DFID from F5x180 on models that provide an F5 sibling. */ + if (cfg->f5_id) { + 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; +} + +/* + * Read the system-wide SocketPresent mask from F1x200[7:0]. + * + * SocketPresent is identical for every DF instance. Read it once from + * the first DF misc device. A zero mask is invalid on supported models. + */ +static int __init hygon_read_socket_present(struct pci_dev *misc, + u8 *socket_present) +{ + u32 reg; + int ret; + + ret =3D hygon_read_df_reg(misc, HYGON_DF_F1, DF_F1_SYSTEM_CFG, ®); + if (ret) + return ret; + + *socket_present =3D reg & 0xff; + if (!*socket_present) { + pr_warn("SocketPresent is zero\n"); + return -EINVAL; + } + + return 0; +} + +static void __init 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); +} + +/* + * Walk all DF misc (F3) devices and read per-node identity (socket_id, + * dfid) from each, collecting them into a flat array. The system-wide + * SocketPresent mask is sampled once, and the enumerated socket set is + * validated against it; socket IDs must also be dense (0..N-1). + */ +static int __init hygon_collect_nodes(struct hygon_node_cache *cache) +{ + struct hygon_node *nodes; + struct pci_dev *misc; + u16 capacity =3D 0, count =3D 0; + u8 observed_socket_mask =3D 0; + u8 socket_present_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; + } + + /* SocketPresent is system-wide, so sample it once. */ + if (!count) { + ret =3D hygon_read_socket_present(misc, + &socket_present_mask); + 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 HYGON_CDD_DFID_BASE); + count++; + + observed_socket_mask |=3D BIT(id.socket_id); + } + + if (count !=3D capacity) { + pr_warn("DF enumeration changed: expected %u nodes, got %u\n", + capacity, count); + ret =3D -ENODEV; + goto fail; + } + + /* + * Every populated socket must contribute at least one enumerated DF + * misc device. socket_present_mask is guaranteed to be non-zero. + */ + if (observed_socket_mask !=3D socket_present_mask) { + pr_warn("SocketPresent mismatch: observed=3D0x%x present=3D0x%x\n", + observed_socket_mask, socket_present_mask); + ret =3D -EINVAL; + goto fail; + } + + cache->nodes =3D nodes; + cache->num_nodes =3D count; + cache->num_sockets =3D hweight8(socket_present_mask); + + if (socket_present_mask !=3D GENMASK(cache->num_sockets - 1, 0)) { + pr_warn("sparse socket IDs not supported (SocketPresent=3D0x%x)\n", + socket_present_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, dfid). + */ +static int __init 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. + * + * A dense CDD mapping requires every socket to contribute the same + * number of compute dies. + */ +static int __init 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_dump_nodes(cache, "before-sort"); + + sort(cache->nodes, cache->num_nodes, sizeof(*cache->nodes), + hygon_node_cmp, NULL); + + for (i =3D 1; i < cache->num_nodes; i++) { + const struct hygon_node *prev =3D &cache->nodes[i - 1]; + const struct hygon_node *node =3D &cache->nodes[i]; + + if (node->socket_id =3D=3D prev->socket_id && + node->dfid =3D=3D prev->dfid) { + pr_warn("duplicate DF node: socket=3D%u dfid=3D%u\n", + node->socket_id, node->dfid); + return -EINVAL; + } + } + + 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 % 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_dump_nodes(cache, "after-sort"); + + return 0; +} + +/* + * Build the global DF node cache. + * + * Called once from hygon_node_init() at fs_initcall, so no locking is + * required. Set ready after node collection and sorting. + */ +static int __init hygon_build_cache(void) +{ + int err; + + err =3D hygon_collect_nodes(&hygon_cache); + if (err) + return err; + + err =3D hygon_sort_and_classify(&hygon_cache); + if (err) + goto fail; + + hygon_cache.ready =3D true; + + return 0; + +fail: + hygon_release_nodes(hygon_cache.nodes, hygon_cache.num_nodes); + hygon_cache.nodes =3D NULL; + hygon_cache.num_nodes =3D 0; + hygon_cache.num_cdd =3D 0; + hygon_cache.num_sockets =3D 0; + return err; +} + +u16 hygon_node_num(void) +{ + return hygon_cache.ready ? hygon_cache.num_nodes : 0; +} +EXPORT_SYMBOL_GPL(hygon_node_num); + +u16 hygon_cdd_num(void) +{ + return hygon_cache.ready ? hygon_cache.num_cdd : 0; +} +EXPORT_SYMBOL_GPL(hygon_cdd_num); + +int hygon_node_get_info(u16 node, struct hygon_node_info *info) +{ + const struct hygon_node *n; + + if (!info) + return -EINVAL; + + if (!hygon_cache.ready) + return -ENODEV; + + if (node >=3D hygon_cache.num_nodes) + return -EINVAL; + + n =3D &hygon_cache.nodes[node]; + info->socket_id =3D n->socket_id; + info->dfid =3D n->dfid; + info->type =3D n->is_cdd ? HYGON_NODE_CDD : HYGON_NODE_IOD; + return 0; +} +EXPORT_SYMBOL_GPL(hygon_node_get_info); + +struct pci_dev *hygon_node_get_func(u16 node, u8 func) +{ + if (!hygon_cache.ready) + 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; + } +} +EXPORT_SYMBOL_GPL(hygon_node_get_func); + +int hygon_pci_dev_to_df_node(struct pci_dev *pdev) +{ + u16 i; + + if (!pdev) + return -EINVAL; + + if (!hygon_cache.ready) + return -ENODEV; + + for (i =3D 0; i < hygon_cache.num_nodes; i++) { + struct pci_dev *misc =3D hygon_cache.nodes[i].misc; + + if (pci_domain_nr(misc->bus) =3D=3D pci_domain_nr(pdev->bus) && + misc->bus->number =3D=3D pdev->bus->number && + PCI_SLOT(misc->devfn) =3D=3D PCI_SLOT(pdev->devfn)) + return i; + } + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(hygon_pci_dev_to_df_node); + +/* + * Build the Hygon DF node cache at fs_initcall. + */ +static int __init hygon_node_init(void) +{ + int ret; + + if (!hygon_get_df_cfg()) { + if (is_hygon_f18h()) + pr_info("Hygon Fam18h model 0x%x is not supported by the node layer\n", + boot_cpu_data.x86_model); + return 0; + } + + ret =3D hygon_build_cache(); + if (ret) { + pr_warn("DF node cache build failed: %d\n", ret); + return ret; + } + + return 0; +} +fs_initcall(hygon_node_init); --=20 2.43.0 From nobody Sat Sep 26 13:09:27 2026 Received: from out28-169.mail.aliyun.com (out28-169.mail.aliyun.com [115.124.28.169]) (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 8BC1F459AD5; Tue, 1 Sep 2026 07:22:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788247378; cv=none; b=AheEta5f6gLIw9YyaUQSg+sOs2Ea+H6NnNajfBYLMx9e8if5PFlTa2e0r0fx6xhG9bL2amjUcUBeUtvAU3kD/Z3XByw6X/AA3+xB8PFsBR3tZttxIbFAEvq9gh6dWriKLUOM8Jd5e+e6Nbp+zbV8njgIjrFu1FOAdz3/3iAIC4U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788247378; c=relaxed/simple; bh=Rt6CARWCsUJX+Jom/I6/vw1DI1CRzJ9Coan0W1eu8DA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BC0ADTw1bVHXRcTdowXxVR5SgPaBsOxvV+xaMPvD4839eBuB8Kv/+PkSEuY7Mh14YsznFuwsIMRS+bIJQ65c0/0WdMjs+zQv/xLUeZfxkTo37Cxv6eVw6lgKi2gx+ZmzlE1IdcE1cL9gerB4hcMtFhzLSLGqBVmGdlLEJ90ff0k= 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.169 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.07437249|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_alarm|0.00632327-0.00115466-0.992522;FP=14413116455023103195|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam011083013073;MF=wanglin@open-hieco.net;NM=1;PH=DS;RN=11;RT=11;SR=0;TI=SMTPD_---.j2BhS6r_1788247049; Received: from localhost.localdomain(mailfrom:wanglin@open-hieco.net fp:SMTPD_---.j2BhS6r_1788247049 cluster:ay29) by smtp.aliyun-inc.com; Tue, 01 Sep 2026 15:17:29 +0800 From: Lin Wang To: Borislav Petkov , yazen.ghannam@amd.com, mario.limonciello@amd.com Cc: tglx@linutronix.de, mingo@redhat.com, dave.hansen@linux.intel.com, "H. Peter Anvin" , bhelgaas@google.com, x86@kernel.org, linux-pci@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 3/5] x86/hygon: Map CPU NodeIds to DF nodes Date: Tue, 1 Sep 2026 15:16:58 +0800 Message-ID: <20260901071700.3255382-4-wanglin@open-hieco.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260901071700.3255382-1-wanglin@open-hieco.net> References: <20260901071700.3255382-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 bare-metal Hygon systems, CPUID 0x8000001E ECX[7:0] encodes a CPU's socket and local CDD ordinal. The value is sparse across sockets and cannot be used as an index into the dense DF node cache. Translate the CPUID value through the CDDs sorted by socket and DFID. The NodeId reported by CPUID in a guest may not describe the physical DF topology. Return -ENODEV in guests or when the NodeId does not identify an enumerated CDD. Signed-off-by: Lin Wang --- arch/x86/include/asm/hygon/node.h | 21 ++++++++++++ arch/x86/kernel/hygon_node.c | 53 +++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/arch/x86/include/asm/hygon/node.h b/arch/x86/include/asm/hygon= /node.h index 6e15c133b774..bc4e588ab405 100644 --- a/arch/x86/include/asm/hygon/node.h +++ b/arch/x86/include/asm/hygon/node.h @@ -82,6 +82,22 @@ u16 hygon_cdd_num(void); */ int hygon_node_get_info(u16 node, struct hygon_node_info *info); =20 +/** + * hygon_cpu_to_df_node() - map CPU to dense DF CDD index + * @cpu: CPU index + * + * Hygon Fam18h exposes sparse physical node IDs via CPUID 8000001E[7:0]. + * This function translates the per-CPU physical node ID into a dense + * DF CDD index in [0, hygon_cdd_num()). The NodeId reported by CPUID in + * a guest may not describe the physical DF topology, so the translation + * is unavailable in guests. + * + * Return: DF CDD index on success, -EINVAL if @cpu is out of range, + * -ENODEV if CPU-to-DF mapping is unavailable or the physical node ID + * does not map to a known DF node. + */ +int hygon_cpu_to_df_node(unsigned int cpu); + /** * hygon_node_get_func() - get DF function PCI device for a node * @node: DF node index in [0, hygon_node_num()) @@ -126,6 +142,11 @@ static inline int hygon_node_get_info(u16 node, struct= hygon_node_info *info) return -ENODEV; } =20 +static inline int hygon_cpu_to_df_node(unsigned int cpu) +{ + return -ENODEV; +} + static inline struct pci_dev *hygon_node_get_func(u16 node, u8 func) { return NULL; diff --git a/arch/x86/kernel/hygon_node.c b/arch/x86/kernel/hygon_node.c index 85835938315b..e2aa7de180e9 100644 --- a/arch/x86/kernel/hygon_node.c +++ b/arch/x86/kernel/hygon_node.c @@ -16,6 +16,8 @@ #define pr_fmt(fmt) "hygon_node: " fmt =20 #include +#include +#include #include #include #include @@ -23,6 +25,7 @@ #include #include #include +#include =20 #include #include @@ -554,6 +557,40 @@ static int __init hygon_sort_and_classify(struct hygon= _node_cache *cache) return 0; } =20 +/* + * Translate a Hygon Fam18h phys_node_id (CPUID 8000001E ECX[7:0]) to a + * dense DF CDD index. On supported models, the node layer uses + * this encoding: + * + * phys_node_id =3D (socket_id << 4) | local_cdd_index_in_dfid_order + * + * The cache->nodes[] CDD region is sorted by (socket_id ASC, dfid ASC), + * so walk it and return the actual cache index of the CDD whose + * socket_id matches and whose socket-local ordinal is @local. + * + * Return -ENODEV if the cache is unavailable or no CDD matches. + */ +static int hygon_phys_nid_to_df_node(unsigned int phys_nid) +{ + unsigned int socket =3D phys_nid >> 4; + unsigned int local =3D phys_nid & 0xf; + unsigned int ordinal =3D 0; + u16 i; + + if (!hygon_cache.ready) + return -ENODEV; + + for (i =3D 0; i < hygon_cache.num_cdd; i++) { + if (hygon_cache.nodes[i].socket_id !=3D socket) + continue; + + if (ordinal++ =3D=3D local) + return i; + } + + return -ENODEV; +} + /* * Build the global DF node cache. * @@ -618,6 +655,22 @@ int hygon_node_get_info(u16 node, struct hygon_node_in= fo *info) } EXPORT_SYMBOL_GPL(hygon_node_get_info); =20 +int hygon_cpu_to_df_node(unsigned int cpu) +{ + if (cpu >=3D nr_cpu_ids) + return -EINVAL; + + /* + * The NodeId reported by CPUID in a guest may not describe the + * physical DF topology. + */ + if (cpu_feature_enabled(X86_FEATURE_HYPERVISOR)) + return -ENODEV; + + return hygon_phys_nid_to_df_node(topology_amd_node_id(cpu)); +} +EXPORT_SYMBOL_GPL(hygon_cpu_to_df_node); + struct pci_dev *hygon_node_get_func(u16 node, u8 func) { if (!hygon_cache.ready) --=20 2.43.0 From nobody Sat Sep 26 13:09:27 2026 Received: from out198-27.us.a.mail.aliyun.com (out198-27.us.a.mail.aliyun.com [47.90.198.27]) (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 77919456E18; Tue, 1 Sep 2026 07:17:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=47.90.198.27 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788247066; cv=none; b=GNHm01sWhQWXN3YOO/rKa2laDn1j14lpLsIgV/63IWBQBJSazCiCSBgYBIqTDs81m+3qO2dXUYMbpYj8LEpkjkEDXSmoTPEwxxTUtZFfpSKj+3Nop6nHr5DCY7yv+oBKiy5ZpEBQj06QOewgXU/QhZ0yLKCj8b7vKxJjgYThYo0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788247066; c=relaxed/simple; bh=qJ1UQCsSl/CtaiffVaPKouOvZNTniSEjJVqDnK+qT2c=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=PD2QkApGEeokEODVgtx1+2wmubBWCpWnHym0VdifLCmeDUaLHSYxcR45jPxDtSsChvS2NdeMrOAiNhIX1V35+g5DIdDhdYcz/O3fufNwtU+YCN+Rtrt89C6ohtNHJAt/TG1L75ZdmtcARWx5kIYcmMyzimba060GxhihQZyvSUg= 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.27 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.07450494|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_system_inform|0.00265924-0.00015051-0.99719;FP=16252303909509023947|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033023018039;MF=wanglin@open-hieco.net;NM=1;PH=DS;RN=11;RT=11;SR=0;TI=SMTPD_---.j2BhS8J_1788247050; Received: from localhost.localdomain(mailfrom:wanglin@open-hieco.net fp:SMTPD_---.j2BhS8J_1788247050 cluster:ay29) by smtp.aliyun-inc.com; Tue, 01 Sep 2026 15:17:30 +0800 From: Lin Wang To: Borislav Petkov , yazen.ghannam@amd.com, mario.limonciello@amd.com Cc: tglx@linutronix.de, mingo@redhat.com, dave.hansen@linux.intel.com, "H. Peter Anvin" , bhelgaas@google.com, x86@kernel.org, linux-pci@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 4/5] x86/amd_node: Skip SMN setup on Hygon Fam18h models 0x04-0x08 Date: Tue, 1 Sep 2026 15:16:59 +0800 Message-ID: <20260901071700.3255382-5-wanglin@open-hieco.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260901071700.3255382-1-wanglin@open-hieco.net> References: <20260901071700.3255382-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" amd_smn_init() distributes host bridge roots according to amd_num_nodes() and PCI enumeration order. Hygon Fam18h models 0x04-0x08 have multiple DF nodes per socket and group SMN roots by socket, so the AMD mapping cannot provide their node-to-root map. Leave the roots unclaimed on these models regardless of CONFIG_HYGON_NODE; the AMD mapping does not apply to their topology. Since amd_num_nodes() is independent of the root array, check smn_exclusive before indexing the array so AMD SMN calls return -ENODEV while its setup is inactive. When built, the Hygon node layer provides the node-to-root mapping. Other Hygon models keep the existing AMD SMN setup. amd_smn_init() can become AMD-only after the remaining Hygon callers have moved to Hygon-specific interfaces. Signed-off-by: Lin Wang --- arch/x86/kernel/amd_node.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/arch/x86/kernel/amd_node.c b/arch/x86/kernel/amd_node.c index 0be01725a2a4..a0cfd74ada03 100644 --- a/arch/x86/kernel/amd_node.c +++ b/arch/x86/kernel/amd_node.c @@ -88,6 +88,9 @@ 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 (!smn_exclusive) + return err; + if (node >=3D amd_num_nodes()) return err; =20 @@ -95,9 +98,6 @@ static int __amd_smn_rw(u8 i_off, u8 d_off, u16 node, u32= address, u32 *value, b if (!root) return err; =20 - if (!smn_exclusive) - return err; - guard(mutex)(&smn_mutex); =20 err =3D pci_write_config_dword(root, i_off, address); @@ -252,6 +252,18 @@ static int __init amd_smn_init(void) if (!cpu_feature_enabled(X86_FEATURE_ZEN)) return 0; =20 + /* + * Hygon models outside this range, including legacy parts, keep the + * existing AMD SMN setup. Models 0x04-0x08 group SMN roots by socket + * instead of amd_num_nodes(), so leave AMD SMN inactive. The Hygon node + * layer, when built, owns their node-to-root mapping. + */ + if (boot_cpu_data.x86_vendor =3D=3D X86_VENDOR_HYGON && + boot_cpu_data.x86 =3D=3D 0x18 && + boot_cpu_data.x86_model >=3D 0x04 && + boot_cpu_data.x86_model <=3D 0x08) + return 0; + guard(mutex)(&smn_mutex); =20 if (amd_roots) --=20 2.43.0 From nobody Sat Sep 26 13:09:27 2026 Received: from out28-196.mail.aliyun.com (out28-196.mail.aliyun.com [115.124.28.196]) (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 875CB444713; Tue, 1 Sep 2026 07:17:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.196 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788247063; cv=none; b=JTW/kxk+m8y40RBKtiH0uVPaNz32Q5ZIJcBHd56BNpjswfsdSg13tOEvfQr9YisPhplUaNJDuBf+w7XnZb911dCu9h2HzLGoEDC9Ue0mWwD0hRFaEEurmx6ZEvzlWTQl7LpDtMmu4j+3tFKFPZxY/w/JjhdhSFqhSN2oT8vilV0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788247063; c=relaxed/simple; bh=oM7jTywyNKXV8MRK/mEIG+T9Y7F/Zg9SscLfN4EgX/E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Vqoy/UHj63WvfLDKj3IJayw7NoWKAHnOAB2ANkeX/DYPBwljNdbjUgKm+jrLiBHTPtyEYeh3T2jP0f5UCMDdWRrMPRmyxUSAdQDll/idGFk5Lr9akyMb/nOPKGSdepaz9v8gTemOWO5nAtjRri+K+CM3mFaDzc+z8qaFTHxhAVg= 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.196 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.00801614-5.535e-05-0.991929;FP=18014790336117556458|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033068005250;MF=wanglin@open-hieco.net;NM=1;PH=DS;RN=11;RT=11;SR=0;TI=SMTPD_---.j2BhS9h_1788247050; Received: from localhost.localdomain(mailfrom:wanglin@open-hieco.net fp:SMTPD_---.j2BhS9h_1788247050 cluster:ay29) by smtp.aliyun-inc.com; Tue, 01 Sep 2026 15:17:31 +0800 From: Lin Wang To: Borislav Petkov , yazen.ghannam@amd.com, mario.limonciello@amd.com Cc: tglx@linutronix.de, mingo@redhat.com, dave.hansen@linux.intel.com, "H. Peter Anvin" , bhelgaas@google.com, x86@kernel.org, linux-pci@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 5/5] x86/hygon: Add Family 0x18 SMN access Date: Tue, 1 Sep 2026 15:17:00 +0800 Message-ID: <20260901071700.3255382-6-wanglin@open-hieco.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260901071700.3255382-1-wanglin@open-hieco.net> References: <20260901071700.3255382-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 DF nodes in a socket share an SMN ingress. The root groups therefore need to be associated with sockets before they can be expanded to the dense DF node indices. Build the node-to-root map after DF enumeration and expose Hygon SMN read and write helpers. Hold a reference and reserve PCI config space for every root used by the built-in node layer. SMN remains unavailable if root discovery or mapping fails. Signed-off-by: Lin Wang --- arch/x86/include/asm/hygon/node.h | 38 ++++- arch/x86/kernel/hygon_node.c | 275 +++++++++++++++++++++++++++++- 2 files changed, 310 insertions(+), 3 deletions(-) diff --git a/arch/x86/include/asm/hygon/node.h b/arch/x86/include/asm/hygon= /node.h index bc4e588ab405..b8a11d85eaee 100644 --- a/arch/x86/include/asm/hygon/node.h +++ b/arch/x86/include/asm/hygon/node.h @@ -2,8 +2,8 @@ /* * Hygon Family 0x18 Data Fabric node enumeration API * - * This header declares Hygon Fam18h DF node enumeration and function - * access interfaces. + * DF node identity, CPU-to-DF-node translation, DF function lookup and + * SMN access for Hygon Family 0x18. */ #ifndef _ASM_X86_HYGON_NODE_H #define _ASM_X86_HYGON_NODE_H @@ -125,6 +125,30 @@ struct pci_dev *hygon_node_get_func(u16 node, u8 func); */ int hygon_pci_dev_to_df_node(struct pci_dev *pdev); =20 +/** + * hygon_smn_read() - read a 32-bit value from a Hygon SMN address + * @node: DF node index in [0, hygon_node_num()) + * @address: SMN address + * @value: output value + * + * Return: 0 on success, -EINVAL if @value is NULL, -ENODEV if Hygon + * SMN is not initialised or @node is out of range, or a negative + * errno from the underlying PCI config access. + */ +int __must_check hygon_smn_read(u16 node, u32 address, u32 *value); + +/** + * hygon_smn_write() - write a 32-bit value to a Hygon SMN address + * @node: DF node index in [0, hygon_node_num()) + * @address: SMN address + * @value: value to write + * + * Return: 0 on success, -ENODEV if Hygon SMN is not initialised or + * @node is out of range, or a negative errno from the underlying + * PCI config access. + */ +int __must_check hygon_smn_write(u16 node, u32 address, u32 value); + #else /* !CONFIG_HYGON_NODE */ =20 static inline u16 hygon_node_num(void) @@ -157,6 +181,16 @@ static inline int hygon_pci_dev_to_df_node(struct pci_= dev *pdev) return -ENODEV; } =20 +static inline int hygon_smn_read(u16 node, u32 address, u32 *value) +{ + return -ENODEV; +} + +static inline int hygon_smn_write(u16 node, u32 address, u32 value) +{ + return -ENODEV; +} + #endif /* CONFIG_HYGON_NODE */ =20 /* Inline helpers, available regardless of CONFIG_HYGON_NODE. */ diff --git a/arch/x86/kernel/hygon_node.c b/arch/x86/kernel/hygon_node.c index e2aa7de180e9..2fcd75ee71e9 100644 --- a/arch/x86/kernel/hygon_node.c +++ b/arch/x86/kernel/hygon_node.c @@ -16,10 +16,12 @@ #define pr_fmt(fmt) "hygon_node: " fmt =20 #include +#include #include #include #include #include +#include #include #include #include @@ -714,8 +716,275 @@ int hygon_pci_dev_to_df_node(struct pci_dev *pdev) EXPORT_SYMBOL_GPL(hygon_pci_dev_to_df_node); =20 /* - * Build the Hygon DF node cache at fs_initcall. + * SMN index/data register pair offsets in the host-bridge PCI config + * space. Reads and writes to a (node, address) pair are issued as a + * two-step transaction: write the SMN address to the index register, + * then read or write the value at the data register. */ +#define HYGON_SMN_INDEX_OFFSET 0x60 +#define HYGON_SMN_DATA_OFFSET 0x64 + +/* + * Runtime SMN state. hygon_smn_exclusive remains false until setup + * succeeds. hygon_smn_reserved_roots owns the independent PCI references + * and config-space reservations for the lifetime of the built-in node + * layer; hygon_smn_roots contains per-node aliases. + */ +static struct pci_dev **hygon_smn_roots; +static struct pci_dev **hygon_smn_reserved_roots; +static u16 hygon_smn_num_nodes; +static bool hygon_smn_exclusive; +static DEFINE_MUTEX(hygon_smn_mutex); + +/* Internal cache accessors used by SMN setup. */ +static u8 __init hygon_node_socket(u16 node) +{ + if (!hygon_cache.ready || node >=3D hygon_cache.num_nodes) + return U8_MAX; + return hygon_cache.nodes[node].socket_id; +} + +static u16 __init hygon_socket_num(void) +{ + return hygon_cache.ready ? hygon_cache.num_sockets : 0; +} + +/* + * Walk PCI host-bridge devices matching the Hygon vendor. The SMN + * index/data registers live in function 0 of each root complex. This + * follows pci_get_class() iterator semantics: @root is consumed and the + * returned device has an iterator reference. A retained device needs an + * independent reference before the iterator advances. + */ +static struct pci_dev * __init hygon_get_next_root(struct pci_dev *root) +{ + while ((root =3D pci_get_class(PCI_CLASS_BRIDGE_HOST << 8, root))) { + if (root->devfn) + continue; + if (root->vendor !=3D PCI_VENDOR_ID_HYGON) + continue; + break; + } + return root; +} + +/* Release each config region before dropping its owning device reference.= */ +static void __init hygon_release_reserved_roots(struct pci_dev **roots, + u16 count) +{ + u16 i; + + for (i =3D 0; i < count; i++) { + pci_release_config_region(roots[i], 0, PCI_CFG_SPACE_SIZE); + pci_dev_put(roots[i]); + } +} + +/* + * Select one root from each contiguous per-socket enumeration group, + * then map every DF node to the root for its socket. The root socket ID + * cannot be read back, so the grouping follows PCI enumeration order. + * + * hygon_smn_reserved_roots owns the PCI references and config regions; + * hygon_smn_roots contains per-node aliases. Enable SMN access only after + * both arrays are complete. + */ +static int __init hygon_smn_setup(void) +{ + struct pci_dev *socket_roots[HYGON_MAX_SOCKETS] =3D { }; + struct pci_dev **reserved_roots, **roots, *owned_root, *root; + u16 count, num_roots, roots_per_socket, node, num_nodes; + u16 num_sockets, reserved, socket; + u8 socket_id; + int ret; + + num_roots =3D 0; + root =3D NULL; + while ((root =3D hygon_get_next_root(root))) + num_roots++; + + pr_debug("Found %u Hygon SMN root devices\n", num_roots); + + if (!num_roots) + return -ENODEV; + + num_nodes =3D hygon_node_num(); + if (!num_nodes) + return -ENODEV; + + num_sockets =3D hygon_socket_num(); + if (!num_sockets) + return -ENODEV; + + if (num_sockets > ARRAY_SIZE(socket_roots)) { + pr_err("Socket count %u exceeds maximum %zu\n", + num_sockets, ARRAY_SIZE(socket_roots)); + return -EINVAL; + } + + if (num_roots % num_sockets) { + pr_err("Root count %u not divisible by socket count %u\n", + num_roots, num_sockets); + return -ENODEV; + } + + roots =3D kcalloc(num_nodes, sizeof(*roots), GFP_KERNEL); + if (!roots) + return -ENOMEM; + + reserved_roots =3D kcalloc(num_roots, sizeof(*reserved_roots), + GFP_KERNEL); + if (!reserved_roots) { + kfree(roots); + return -ENOMEM; + } + + /* + * Keep the first of every roots_per_socket consecutive roots and + * skip the rest. This groups roots by enumeration order, relying on + * the platform enumerating each socket's roots contiguously. Roots + * within the same socket are redundant SMN ingress points. + */ + roots_per_socket =3D num_roots / num_sockets; + socket =3D 0; + reserved =3D 0; + count =3D 0; + root =3D NULL; + while ((root =3D hygon_get_next_root(root))) { + if (reserved >=3D num_roots) { + ret =3D -ENODEV; + pci_dev_put(root); + goto err_release; + } + + pci_dbg(root, "Reserving PCI config space\n"); + + /* + * Mark the entire PCI config space kernel-exclusive because it + * contains the SMN index/data registers. + */ + if (!pci_request_config_region_exclusive(root, 0, + PCI_CFG_SPACE_SIZE, + NULL)) { + pci_err(root, "Failed to reserve config space\n"); + ret =3D -EEXIST; + /* This exit does not advance the iterator. */ + pci_dev_put(root); + goto err_release; + } + + owned_root =3D pci_dev_get(root); + reserved_roots[reserved++] =3D owned_root; + + if (count++ % roots_per_socket) + continue; + + if (socket >=3D num_sockets) { + ret =3D -ENODEV; + pci_dev_put(root); + goto err_release; + } + + pci_dbg(root, "is root for Hygon socket %u\n", socket); + socket_roots[socket++] =3D owned_root; + } + + if (reserved !=3D num_roots || socket !=3D num_sockets) { + pr_err("Root enumeration changed: expected %u roots/%u sockets, got %u/%= u\n", + num_roots, num_sockets, reserved, socket); + ret =3D -ENODEV; + goto err_release; + } + + 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_release; + } + + pci_dbg(socket_roots[socket_id], + "is root for Hygon node %u (socket %u)\n", + node, socket_id); + roots[node] =3D socket_roots[socket_id]; + } + + hygon_smn_reserved_roots =3D reserved_roots; + hygon_smn_roots =3D roots; + hygon_smn_num_nodes =3D num_nodes; + hygon_smn_exclusive =3D true; + return 0; + +err_release: + hygon_release_reserved_roots(reserved_roots, reserved); + kfree(reserved_roots); + kfree(roots); + return ret; +} + +/* + * Serialize the PCI index/data pair between in-kernel SMN users. + * The transaction follows amd_smn_read/write(); + * hygon_smn_setup() provides the Hygon node-to-root mapping. + */ +static int __hygon_smn_rw(u16 node, u32 address, u32 *value, bool write) +{ + struct pci_dev *root; + int err; + + if (!hygon_smn_exclusive || node >=3D hygon_smn_num_nodes) + return -ENODEV; + + root =3D hygon_smn_roots[node]; + if (!root) + return -ENODEV; + + guard(mutex)(&hygon_smn_mutex); + + err =3D pci_write_config_dword(root, HYGON_SMN_INDEX_OFFSET, address); + if (err) { + pr_warn("SMN index write failed (addr 0x%x)\n", address); + return pcibios_err_to_errno(err); + } + + err =3D write ? pci_write_config_dword(root, HYGON_SMN_DATA_OFFSET, *valu= e) + : pci_read_config_dword(root, HYGON_SMN_DATA_OFFSET, value); + + return pcibios_err_to_errno(err); +} + +int hygon_smn_read(u16 node, u32 address, u32 *value) +{ + int err; + + if (!value) + return -EINVAL; + + err =3D __hygon_smn_rw(node, address, value, false); + + /* Clear the output so callers do not consume a stale value. */ + if (err) { + *value =3D 0; + return err; + } + + /* Treat the PCI all-ones value as a missing device. */ + if (PCI_POSSIBLE_ERROR(*value)) { + *value =3D 0; + return -ENODEV; + } + + return 0; +} +EXPORT_SYMBOL_GPL(hygon_smn_read); + +int hygon_smn_write(u16 node, u32 address, u32 value) +{ + return __hygon_smn_rw(node, address, &value, true); +} +EXPORT_SYMBOL_GPL(hygon_smn_write); + static int __init hygon_node_init(void) { int ret; @@ -733,6 +1002,10 @@ static int __init hygon_node_init(void) return ret; } =20 + ret =3D hygon_smn_setup(); + if (ret) + pr_warn("SMN setup failed: %d\n", ret); + return 0; } fs_initcall(hygon_node_init); --=20 2.43.0