From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=115.124.28.100 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793252017; b=rlhmVQqCLqh2dvicEReCnw3pMxybSFX/XmQVcRlbvBncUQGIed6/GJfiMpgKPFxXYP4/ nc3rEVoJWZf4/elvHHsLStqRlTfiqJoQRaqt+muUONUGW44KOQzJ+ztNKShzVo3YKV27X Zc2P8yb+aM+tTmdkKJXuk1i5H7YB5kytp14HCt5OWCF8Nfk4FUSzdwA5vu6GlO9YAJFrE gwiUi7k+2eir9pABU/lR6S0Cwl3KOl5r4SPSms6D1NQuxWz92VI3ZpzhFCgpF7m/3Iz8A ns9jpIvvD2mAcEyeRiMC3hUURd3X2izMwjFJR0FONbLAINcvKFdCn0E8SH6n9qXtq+A== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793252017; h=DMARC-Filter:X-Alimail-AntiSpam:Received:From:To:Cc:Subject:Date: Message-ID:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding; bh=zE/whNCLO+BcmqUPDILZNp73gITyebazvBvv7275/Vk=; b=FQV4kbO+91j3il+gRH1SuvHLM4CgETuAQEow80EsUYHKqhakfvGMaVmxPjI+RdkW4TQi vlOn4UB6JjR+3jWbBa4KBBXB++axUzFo3h9WmlqDk5hDs+1ZGF6LgTBoxrWkOJDizjeQz JCfnr4RzBE5QgOm3RsI6S8rjNB0qMoT/ob7jc0ilsgGhM5afMm/0gapnfjo6o5zyBY3Q7 JFQ04PTI9NsDN0uMxJ4K/kN6FRb/fx8vqo8jly7s8SBepum+OOCpgWDBhAMExMTOpyiw6 Dc6yxP23Qk0cYF66dRdb+i88I8j57paVnkz6V3NHzjW3L9NFluRdjePC7bPxyRZjf/g== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none header.from=open-hieco.net; spf=pass smtp.mailfrom=open-hieco.net; arc=none smtp.remote-ip=115.124.28.100 Received: from out28-100.mail.aliyun.com (out28-100.mail.aliyun.com [115.124.28.100]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B38AB606A31D; Thu, 29 Oct 2026 05:33:34 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org B38AB606A31D 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.0158118-0.000374529-0.983814;FP=14063471130726647034|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033071099199;MF=wanglin@open-hieco.net;NM=1;PH=DS;RN=12;RT=12;SR=0;TI=SMTPD_---.js2Zbsf_1793252004; Received: from localhost.localdomain(mailfrom:wanglin@open-hieco.net fp:SMTPD_---.js2Zbsf_1793252004 cluster:ay29) by smtp.aliyun-inc.com; Thu, 29 Oct 2026 13:33:25 +0800 From: Lin Wang To: Borislav Petkov , Yazen Ghannam , Mario Limonciello Cc: Pu Wen , Thomas Gleixner , Ingo Molnar , Dave Hansen , "H. Peter Anvin" , Tony Luck , x86@kernel.org, linux-edac@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v9 1/7] x86/hygon: Add Family 0x18 DF node enumeration Date: Thu, 29 Oct 2026 13:33:07 +0800 Message-ID: <20261029053313.46323-2-wanglin@open-hieco.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20261029053313.46323-1-wanglin@open-hieco.net> References: <20261029053313.46323-1-wanglin@open-hieco.net> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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. Enumerate Hygon DF devices by PCI ID and read their identities from DF registers. Build a dense node cache with Core and DDR Dies (CDDs) before I/O Dies (IODs), providing node counts, identity and PCI function lookups. Guests may expose a supported CPU model without DF devices. Suppress the cache-build warning for -ENODEV in guests; keep other initialization errors visible. The node lookup helpers remain local until Hygon consumers are added. Signed-off-by: Lin Wang Reviewed-by: Yazen Ghannam --- MAINTAINERS | 7 + arch/x86/Kconfig | 4 + arch/x86/include/asm/hygon/node.h | 41 ++ arch/x86/kernel/Makefile | 1 + arch/x86/kernel/hygon_node.c | 599 ++++++++++++++++++++++++++++++ 5 files changed, 652 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 748ce4d1c6a2..34b61172bd97 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12392,6 +12392,13 @@ S: Maintained F: Documentation/devicetree/bindings/input/touchscreen/hycon,hy46xx.yaml F: drivers/input/touchscreen/hycon-hy46xx.c +HYGON NODE DRIVER +M: Lin Wang +L: linux-kernel@vger.kernel.org +S: Maintained +F: arch/x86/include/asm/hygon/node.h +F: arch/x86/kernel/hygon_node.c + HYGON PROCESSOR SUPPORT M: Pu Wen L: linux-kernel@vger.kernel.org diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 58dce0b66179..7d71c8ae0582 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -3082,6 +3082,10 @@ config AMD_NODE def_bool y depends on CPU_SUP_AMD && PCI +config HYGON_NODE + def_bool y + depends on CPU_SUP_HYGON && PCI + endmenu 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..8cabbbdcf96a --- /dev/null +++ b/arch/x86/include/asm/hygon/node.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Hygon Family 0x18 Data Fabric (DF) node interfaces */ +#ifndef _ASM_X86_HYGON_NODE_H +#define _ASM_X86_HYGON_NODE_H + +#include + +/* DF function indices supported by hygon_node_get_func(). */ +#define HYGON_DF_F3 3 +#define HYGON_DF_F4 4 + +/* + * 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 >= HYGON_CDD_DFID_BASE); + * contains CPU cores and UMC controllers. + */ +enum hygon_node_type { + HYGON_NODE_IOD = 0, + HYGON_NODE_CDD = 1, +}; + +/** + * struct hygon_node_info - DF node information + * @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; +}; + +#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) += hpet.o obj-$(CONFIG_AMD_NB) += amd_nb.o obj-$(CONFIG_AMD_NODE) += amd_node.o +obj-$(CONFIG_HYGON_NODE) += hygon_node.o obj-$(CONFIG_DEBUG_NMI_SELFTEST) += nmi_selftest.o obj-$(CONFIG_KVM_GUEST) += kvm.o kvmclock.o diff --git a/arch/x86/kernel/hygon_node.c b/arch/x86/kernel/hygon_node.c new file mode 100644 index 000000000000..6a5278f8e5ca --- /dev/null +++ b/arch/x86/kernel/hygon_node.c @@ -0,0 +1,599 @@ +// 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 +#include + +/* + * DF register offsets used for node identity discovery. + * + * F1x200 (SystemCfg) -- present on all supported models: + * [30:28] MySocketId - hardware socket ID + * [23:20] MyDieId - die ID (equals DFID on some models, see below) + * + * F5x180 (FabricBlockInstanceInformation3_CS) -- used on Model 06h-08h: + * [19:16] DFID - Data Fabric ID for UMC/SMN addressing + * + * DFID source by model: + * Model 04h/05h: F1x200[23:20] (MyDieId == DFID) + * Model 06h-08h: F5x180[19:16] (MyDieId != DFID, different numbering) + */ +#define DF_F1_SYSTEM_CFG 0x200 +#define DF_F5_FABRIC_ID 0x180 + +#define DF_F1_SOCKET_ID GENMASK(30, 28) +#define DF_F1_DIE_ID GENMASK(23, 20) +#define DF_F1_SOCKET_PRESENT GENMASK(7, 0) +#define DF_F5_DFID GENMASK(19, 16) + +/* DF function numbers for sibling device access (internal use). */ +#define DF_F1 1 +#define DF_F5 5 + +/* PCI device IDs used only by Hygon DF node discovery. */ +#define PCI_DEVICE_ID_HYGON_18H_DF_F3 0x1463 +#define PCI_DEVICE_ID_HYGON_18H_DF_F4 0x1464 +#define PCI_DEVICE_ID_HYGON_18H_M04H_DF_F1 0x1491 +#define PCI_DEVICE_ID_HYGON_18H_M04H_DF_F3 0x1493 +#define PCI_DEVICE_ID_HYGON_18H_M04H_DF_F4 0x1494 +#define PCI_DEVICE_ID_HYGON_18H_M05H_DF_F1 0x14b1 +#define PCI_DEVICE_ID_HYGON_18H_M05H_DF_F3 0x14b3 +#define PCI_DEVICE_ID_HYGON_18H_M05H_DF_F4 0x14b4 +#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. + * The socket ID and DFID, read from DF registers, together identify + * the node in hardware. + */ +struct df_node { + struct pci_dev *misc; + struct pci_dev *link; + u8 socket_id; + u8 dfid; + bool is_cdd; +}; + +struct df_node_cache { + struct df_node *nodes; + u16 num_nodes; + u16 num_cdd; + u16 num_sockets; +}; + +struct df_id { + u8 socket_id; + u8 dfid; +}; + +/* DF sibling device IDs used to read node identity. */ +struct df_cfg { + u16 f1_id; + u16 f5_id; +}; + +/* DF misc (F3) device IDs for all supported Hygon Family 0x18 models. */ +static const struct pci_device_id df_misc_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_DF_F3) }, + { 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_M05H_DF_F3) }, + {} +}; + +/* DF link (F4) device IDs for supported models. */ +static const struct pci_device_id df_link_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_HYGON_18H_DF_F4) }, + { 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_M05H_DF_F4) }, + {} +}; + +static const struct df_cfg m04_df_cfg __initconst = { + .f1_id = PCI_DEVICE_ID_HYGON_18H_M04H_DF_F1, +}; + +static const struct df_cfg m05_df_cfg __initconst = { + .f1_id = PCI_DEVICE_ID_HYGON_18H_M05H_DF_F1, +}; + +static const struct df_cfg m06_m08_df_cfg __initconst = { + .f1_id = PCI_DEVICE_ID_HYGON_18H_M05H_DF_F1, + .f5_id = PCI_DEVICE_ID_HYGON_18H_M06H_DF_F5, +}; + +static const struct x86_cpu_id df_cpuids[] __initconst = { + X86_MATCH_VENDOR_FAM_MODEL(HYGON, 0x18, 0x04, &m04_df_cfg), + X86_MATCH_VENDOR_FAM_MODEL(HYGON, 0x18, 0x05, &m05_df_cfg), + X86_MATCH_VENDOR_FAM_MODEL(HYGON, 0x18, 0x06, &m06_m08_df_cfg), + X86_MATCH_VENDOR_FAM_MODEL(HYGON, 0x18, 0x07, &m06_m08_df_cfg), + X86_MATCH_VENDOR_FAM_MODEL(HYGON, 0x18, 0x08, &m06_m08_df_cfg), + {} +}; + +static struct df_node_cache df_cache; + +/* + * 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_df_dev(struct pci_dev *from, const struct pci_device_id *ids) +{ + while ((from = pci_get_device(PCI_VENDOR_ID_HYGON, PCI_ANY_ID, from))) { + if (pci_match_id(ids, from)) + return from; + } + + return NULL; +} + +/* + * Find the DF link (function 4) sibling of a DF misc (function 3) + * device. Both functions share the same PCI bus and slot. + */ +static struct pci_dev * __init get_link(struct pci_dev *misc) +{ + struct pci_dev *link; + + link = pci_get_domain_bus_and_slot(pci_domain_nr(misc->bus), + misc->bus->number, + PCI_DEVFN(PCI_SLOT(misc->devfn), + HYGON_DF_F4)); + if (!link) + return NULL; + + if (!pci_match_id(df_link_ids, link)) { + pci_dev_put(link); + return NULL; + } + + return link; +} + +static const struct df_cfg * __init get_df_cfg(void) +{ + const struct x86_cpu_id *id = x86_match_cpu(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 read_df_reg(struct pci_dev *misc, u8 func, int offset, u32 *value) +{ + const struct df_cfg *cfg; + struct pci_dev *sibling; + u16 expected_device; + int err; + + cfg = get_df_cfg(); + if (!cfg) + return -ENODEV; + + if (func == DF_F1) { + expected_device = cfg->f1_id; + + /* + * Model 05h uses the M04H F1 device ID when F3 does not use + * the M05H device ID. + */ + if (boot_cpu_data.x86_model == 0x5 && + misc->device != PCI_DEVICE_ID_HYGON_18H_M05H_DF_F3) + expected_device = PCI_DEVICE_ID_HYGON_18H_M04H_DF_F1; + } else if (func == DF_F5) { + expected_device = cfg->f5_id; + } else { + return -EINVAL; + } + + if (!expected_device) + return -ENODEV; + + sibling = pci_get_domain_bus_and_slot(pci_domain_nr(misc->bus), + misc->bus->number, + PCI_DEVFN(PCI_SLOT(misc->devfn), + func)); + if (!sibling) + return -ENODEV; + + if (sibling->vendor != PCI_VENDOR_ID_HYGON || + sibling->device != expected_device) { + pci_dev_put(sibling); + return -ENODEV; + } + + err = pci_read_config_dword(sibling, offset, value); + pci_dev_put(sibling); + + if (err) { + pr_warn("error reading %04x:%02x:%02x.%u offset 0x%x\n", + pci_domain_nr(misc->bus), misc->bus->number, + PCI_SLOT(misc->devfn), func, offset); + return pcibios_err_to_errno(err); + } + + /* 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. + * + * Supported 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]. + * + * The boot CPU model selects the DF register layout for all instances. + */ +static int __init read_df_id(struct pci_dev *misc, struct df_id *id) +{ + const struct df_cfg *cfg = get_df_cfg(); + u32 reg; + int ret; + + if (!cfg) + return -ENODEV; + + ret = read_df_reg(misc, DF_F1, DF_F1_SYSTEM_CFG, ®); + if (ret) + return ret; + + id->socket_id = FIELD_GET(DF_F1_SOCKET_ID, reg); + id->dfid = FIELD_GET(DF_F1_DIE_ID, reg); + + /* Read DFID from F5x180 on models that provide an F5 sibling. */ + if (cfg->f5_id) { + ret = read_df_reg(misc, DF_F5, DF_F5_FABRIC_ID, ®); + if (ret) + return ret; + id->dfid = FIELD_GET(DF_F5_DFID, reg); + } + + return 0; +} + +/* + * Read the system-wide SocketPresent mask from F1x200[7:0]. + * + * SocketPresent is system-wide rather than per-node. Read it once from + * the first DF misc device. A zero mask is invalid on supported models. + */ +static int __init read_socket_present(struct pci_dev *misc, u8 *socket_present) +{ + u32 reg; + int ret; + + ret = read_df_reg(misc, DF_F1, DF_F1_SYSTEM_CFG, ®); + if (ret) + return ret; + + *socket_present = FIELD_GET(DF_F1_SOCKET_PRESENT, reg); + if (!*socket_present) { + pr_warn("SocketPresent is zero\n"); + return -EINVAL; + } + + return 0; +} + +static void __init release_nodes(struct df_node *nodes, u16 count) +{ + u16 i; + + for (i = 0; i < count; i++) { + pci_dev_put(nodes[i].misc); + pci_dev_put(nodes[i].link); + } + + kfree(nodes); +} + +/* + * 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 collect_nodes(struct df_node_cache *cache) +{ + u8 observed_socket_mask = 0, socket_present_mask = 0; + struct pci_dev *misc = NULL, *link = NULL; + u16 capacity = 0, count = 0; + struct df_node *nodes; + int ret; + + while ((misc = next_df_dev(misc, df_misc_ids))) + capacity++; + + if (!capacity) + return -ENODEV; + + nodes = kzalloc_objs(*nodes, capacity); + if (!nodes) + return -ENOMEM; + + while ((misc = next_df_dev(misc, df_misc_ids))) { + struct df_id id; + + link = get_link(misc); + if (!link) { + ret = -ENODEV; + goto fail_put_devs; + } + + ret = read_df_id(misc, &id); + if (ret) + goto fail_put_devs; + + /* SocketPresent is system-wide, so sample it once. */ + if (!count) { + ret = read_socket_present(misc, &socket_present_mask); + if (ret) + goto fail_put_devs; + } + + if (count >= capacity) { + ret = -ENODEV; + goto fail_put_devs; + } + + nodes[count].misc = pci_dev_get(misc); + nodes[count].link = link; + link = NULL; + nodes[count].socket_id = id.socket_id; + nodes[count].dfid = id.dfid; + nodes[count].is_cdd = (id.dfid >= HYGON_CDD_DFID_BASE); + count++; + + observed_socket_mask |= BIT(id.socket_id); + } + + if (count != capacity) { + pr_warn("DF enumeration changed: expected %u nodes, got %u\n", + capacity, count); + ret = -ENODEV; + goto fail; + } + + /* + * Every populated socket must contribute at least one enumerated DF + * misc device. SocketPresent is guaranteed to be non-zero. + */ + if (observed_socket_mask != socket_present_mask) { + pr_warn("SocketPresent mismatch: observed=0x%x present=0x%x\n", + observed_socket_mask, socket_present_mask); + ret = -EINVAL; + goto fail; + } + + cache->nodes = nodes; + cache->num_nodes = count; + cache->num_sockets = hweight8(socket_present_mask); + + if (socket_present_mask != GENMASK(cache->num_sockets - 1, 0)) { + pr_warn("sparse socket IDs not supported (SocketPresent=0x%x)\n", + socket_present_mask); + ret = -EINVAL; + goto fail; + } + + return 0; + +fail_put_devs: + pci_dev_put(link); + pci_dev_put(misc); +fail: + release_nodes(nodes, count); + cache->nodes = NULL; + cache->num_nodes = 0; + cache->num_sockets = 0; + return ret; +} + +/* + * Sort CDD nodes before IOD nodes, then order by hardware + * (socket_id, dfid). + */ +static int __init node_cmp(const void *a, const void *b) +{ + const struct df_node *left = a, *right = b; + + if (left->is_cdd != right->is_cdd) + return right->is_cdd - left->is_cdd; + + if (left->socket_id != right->socket_id) + return (int)left->socket_id - right->socket_id; + + return (int)left->dfid - (int)right->dfid; +} + +static int __init sort_and_classify(struct df_node_cache *cache) +{ + u16 i; + + sort(cache->nodes, cache->num_nodes, sizeof(*cache->nodes), + node_cmp, NULL); + + cache->num_cdd = 0; + + for (i = 0; i < cache->num_nodes; i++) { + const struct df_node *node = &cache->nodes[i]; + + if (i) { + const struct df_node *prev = &cache->nodes[i - 1]; + + if (node->socket_id == prev->socket_id && + node->dfid == prev->dfid) { + pr_warn("duplicate DF node: socket=%u dfid=%u\n", + node->socket_id, node->dfid); + return -EINVAL; + } + } + + if (node->is_cdd) + cache->num_cdd++; + } + + if (!cache->num_cdd) + return -ENODEV; + + return 0; +} + +static int __init build_cache(void) +{ + struct df_node_cache cache = { }; + int err; + + err = collect_nodes(&cache); + if (err) + return err; + + err = sort_and_classify(&cache); + if (err) + goto fail; + + df_cache = cache; + + return 0; + +fail: + release_nodes(cache.nodes, cache.num_nodes); + return err; +} + +static u16 __maybe_unused hygon_node_num(void) +{ + return df_cache.num_nodes; +} + +static u16 __maybe_unused hygon_cdd_num(void) +{ + return df_cache.num_cdd; +} + +static int __maybe_unused hygon_node_get_info(u16 node, struct hygon_node_info *info) +{ + const struct df_node *n; + + if (!info) + return -EINVAL; + + if (!df_cache.nodes) + return -ENODEV; + + if (node >= df_cache.num_nodes) + return -EINVAL; + + n = &df_cache.nodes[node]; + info->socket_id = n->socket_id; + info->dfid = n->dfid; + info->type = n->is_cdd ? HYGON_NODE_CDD : HYGON_NODE_IOD; + return 0; +} + +/* Return a referenced DF device; the caller must call pci_dev_put(). */ +static __maybe_unused struct pci_dev *hygon_node_get_func(u16 node, u8 func) +{ + if (!df_cache.nodes) + return NULL; + + if (node >= df_cache.num_nodes) + return NULL; + + switch (func) { + case HYGON_DF_F3: + return pci_dev_get(df_cache.nodes[node].misc); + case HYGON_DF_F4: + return pci_dev_get(df_cache.nodes[node].link); + default: + return NULL; + } +} + +/* Map any sibling PCI function to its dense DF node index. */ +static int __maybe_unused hygon_pci_dev_to_df_node(struct pci_dev *pdev) +{ + u16 i; + + if (!pdev) + return -EINVAL; + + if (!df_cache.nodes) + return -ENODEV; + + for (i = 0; i < df_cache.num_nodes; i++) { + struct pci_dev *misc = df_cache.nodes[i].misc; + + if (pci_domain_nr(misc->bus) == pci_domain_nr(pdev->bus) && + misc->bus->number == pdev->bus->number && + PCI_SLOT(misc->devfn) == PCI_SLOT(pdev->devfn)) + return i; + } + + return -ENODEV; +} + +static int __init df_node_init(void) +{ + int ret; + + if (!get_df_cfg()) + return 0; + + ret = build_cache(); + if (ret) { + if (ret != -ENODEV || !cpu_feature_enabled(X86_FEATURE_HYPERVISOR)) + pr_warn("DF node cache build failed: %d\n", ret); + return ret; + } + + pr_info("%u DF nodes (%u CDDs, %u IODs) across %u sockets\n", + df_cache.num_nodes, df_cache.num_cdd, + df_cache.num_nodes - df_cache.num_cdd, + df_cache.num_sockets); + + return 0; +} +fs_initcall(df_node_init); -- 2.43.0