mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Yazen Ghannam <yazen.ghannam@amd.com>
To: Lin Wang <wanglin@open-hieco.net>
Cc: Borislav Petkov <bp@alien8.de>,
	Mario Limonciello <mario.limonciello@amd.com>,
	Pu Wen <puwen@hygon.cn>, Thomas Gleixner <tglx@kernel.org>,
	Ingo Molnar <mingo@redhat.com>,
	Dave Hansen <dave.hansen@linux.intel.com>,
	"H. Peter Anvin" <hpa@zytor.com>, Tony Luck <tony.luck@intel.com>,
	x86@kernel.org, linux-edac@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH v7 7/7] x86/hygon: Add Family 0x18 SMN access
Date: Sat, 17 Oct 2026 11:41:40 -0400	[thread overview]
Message-ID: <20261017154140.GF359037@yaz-khff2.amd.com> (raw)
In-Reply-To: <20261009090111.151878-8-wanglin@open-hieco.net>

On Fri, Oct 09, 2026 at 05:01:11PM +0800, Lin Wang wrote:
> All DF nodes in a Hygon socket share one SMN ingress, so SMN accesses
> for a node have to go through a root of the node's socket rather than a
> root per node.
> 
> After DF enumeration, map each DF node to the first root of its socket.
> The root socket ID cannot be read back, so group the roots by socket
> in PCI enumeration order. Hold a reference to every root, reserve its
> config space, and serialize each index/data transaction. Enable SMN
> access only after setup succeeds; on failure the DF node cache remains
> usable without it.
> 
> Signed-off-by: Lin Wang <wanglin@open-hieco.net>
> ---
>  arch/x86/include/asm/hygon/node.h |  36 +++++
>  arch/x86/kernel/hygon_node.c      | 233 +++++++++++++++++++++++++++++-
>  2 files changed, 268 insertions(+), 1 deletion(-)
> 
> diff --git a/arch/x86/include/asm/hygon/node.h b/arch/x86/include/asm/hygon/node.h
> index 5a0a86e90ea2..321a40d41ed7 100644
> --- a/arch/x86/include/asm/hygon/node.h
> +++ b/arch/x86/include/asm/hygon/node.h
> @@ -96,6 +96,30 @@ struct pci_dev *hygon_node_get_func(u16 node, u8 func);
>   */
>  int hygon_pci_dev_to_df_node(struct pci_dev *pdev);
>  
> +/**
> + * 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 initialized 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 initialized 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 */
>  
>  static inline u16 hygon_node_num(void)
> @@ -123,6 +147,18 @@ static inline int hygon_pci_dev_to_df_node(struct pci_dev *pdev)
>  	return -ENODEV;
>  }
>  
> +static inline int __must_check hygon_smn_read(u16 node, u32 address,
> +					      u32 *value)
> +{
> +	return -ENODEV;
> +}
> +
> +static inline int __must_check hygon_smn_write(u16 node, u32 address,
> +					       u32 value)
> +{
> +	return -ENODEV;
> +}
> +
>  #endif /* CONFIG_HYGON_NODE */
>  
>  #endif /* _ASM_X86_HYGON_NODE_H */
> diff --git a/arch/x86/kernel/hygon_node.c b/arch/x86/kernel/hygon_node.c
> index 4d492513a433..b4afa296b4ca 100644
> --- a/arch/x86/kernel/hygon_node.c
> +++ b/arch/x86/kernel/hygon_node.c
> @@ -1,6 +1,6 @@
>  // SPDX-License-Identifier: GPL-2.0-only
>  /*
> - * Hygon Family 0x18 Data Fabric node enumeration
> + * Hygon Family 0x18 Data Fabric node enumeration and SMN access
>   *
>   * A DF instance exposes sibling PCI functions in one slot. Function 3
>   * is the misc device used for enumeration, function 4 is the link
> @@ -17,9 +17,11 @@
>  
>  #include <linux/bitfield.h>
>  #include <linux/bitops.h>
> +#include <linux/cleanup.h>
>  #include <linux/cpufeature.h>
>  #include <linux/export.h>
>  #include <linux/init.h>
> +#include <linux/mutex.h>
>  #include <linux/pci.h>
>  #include <linux/pci_ids.h>
>  #include <linux/processor.h>
> @@ -633,6 +635,231 @@ int hygon_pci_dev_to_df_node(struct pci_dev *pdev)
>  }
>  EXPORT_SYMBOL_GPL(hygon_pci_dev_to_df_node);
>  
> +/*
> + * 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 SMN_INDEX_OFFSET	0x60
> +#define SMN_DATA_OFFSET		0x64
> +
> +/*
> + * Runtime SMN state.
> + *
> + * The smn_reserved_roots array owns the independent PCI references and
> + * config-space reservations for the lifetime of the built-in node layer. The
> + * smn_roots array contains per-node aliases and is published only after
> + * setup succeeds.
> + */
> +static struct pci_dev	**smn_roots;
> +static struct pci_dev	**smn_reserved_roots;
> +static u16		smn_num_nodes;
> +static DEFINE_MUTEX(smn_mutex);
> +
> +/*
> + * 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 get_next_root(struct pci_dev *root)
> +{
> +	while ((root = pci_get_class(PCI_CLASS_BRIDGE_HOST << 8, root))) {
> +		if (root->devfn)
> +			continue;
> +		if (root->vendor != PCI_VENDOR_ID_HYGON)
> +			continue;
> +		break;
> +	}
> +	return root;
> +}
> +
> +/* Release each config region before dropping its owning device reference. */
> +static void __init release_reserved_roots(struct pci_dev **roots, u16 count)
> +{
> +	u16 i;
> +
> +	for (i = 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.
> + *
> + * The smn_reserved_roots array owns the PCI references and config
> + * regions. The smn_roots array contains per-node aliases. Enable SMN
> + * access only after both arrays are complete.
> + */
> +static int __init smn_setup(void)
> +{
> +	u16 num_roots, roots_per_socket, node, num_nodes, num_sockets, reserved;
> +	struct pci_dev **reserved_roots, **roots;
> +	struct pci_dev *root = NULL;
> +	u8 socket_id;
> +	int ret;
> +
> +	num_roots = 0;
> +	while ((root = get_next_root(root)))
> +		num_roots++;
> +
> +	pr_debug("Found %u Hygon SMN root devices\n", num_roots);
> +
> +	if (!num_roots)
> +		return -ENODEV;
> +
> +	num_nodes = df_cache.num_nodes;
> +	if (!num_nodes)
> +		return -ENODEV;
> +
> +	num_sockets = df_cache.num_sockets;
> +	if (!num_sockets)
> +		return -ENODEV;
> +
> +	if (num_roots % num_sockets) {
> +		pr_err("Root count %u not divisible by socket count %u\n",
> +		       num_roots, num_sockets);
> +		return -ENODEV;
> +	}
> +
> +	roots = kzalloc_objs(*roots, num_nodes);
> +	if (!roots)
> +		return -ENOMEM;
> +
> +	reserved_roots = kzalloc_objs(*reserved_roots, num_roots);
> +	if (!reserved_roots) {
> +		kfree(roots);
> +		return -ENOMEM;
> +	}
> +
> +	/* Reserve every root before publishing the node-to-root mapping. */
> +	roots_per_socket = num_roots / num_sockets;
> +	reserved = 0;
> +	while ((root = get_next_root(root))) {
> +		if (reserved >= num_roots) {
> +			ret = -ENODEV;
> +			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 = -EEXIST;
> +			goto err_release;
> +		}
> +
> +		reserved_roots[reserved++] = pci_dev_get(root);
> +	}
> +
> +	if (reserved != num_roots) {
> +		pr_err("Root enumeration changed: expected %u roots, got %u\n",
> +		       num_roots, reserved);
> +		ret = -ENODEV;
> +		goto err_release;
> +	}
> +
> +	for (node = 0; node < num_nodes; node++) {
> +		socket_id = df_cache.nodes[node].socket_id;
> +
> +		if (socket_id >= num_sockets) {
> +			ret = -ENODEV;
> +			goto err_release;
> +		}
> +
> +		roots[node] = reserved_roots[socket_id * roots_per_socket];
> +
> +		pci_dbg(roots[node],
> +			"is root for Hygon node %u (socket %u)\n",
> +			node, socket_id);
> +	}
> +
> +	smn_reserved_roots = reserved_roots;
> +	smn_num_nodes = num_nodes;
> +	smn_roots = roots;
> +	return 0;
> +
> +err_release:
> +	pci_dev_put(root);
> +	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 access sequence follows amd_smn_read() and amd_smn_write().
> + * The Hygon node-to-root mapping comes from smn_setup().
> + */
> +static int smn_rw(u16 node, u32 address, u32 *value, bool write)
> +{
> +	struct pci_dev *root;
> +	int err;
> +
> +	if (!smn_roots || node >= smn_num_nodes)
> +		return -ENODEV;
> +
> +	root = smn_roots[node];
> +	if (!root)
> +		return -ENODEV;
> +
> +	guard(mutex)(&smn_mutex);
> +
> +	err = pci_write_config_dword(root, SMN_INDEX_OFFSET, address);
> +	if (err) {
> +		pr_warn("SMN index write failed (addr 0x%x)\n", address);
> +		return pcibios_err_to_errno(err);
> +	}
> +
> +	err = write ? pci_write_config_dword(root, SMN_DATA_OFFSET, *value)
> +		    : pci_read_config_dword(root, 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 = smn_rw(node, address, value, false);
> +
> +	/* Clear the output so callers do not consume a stale value. */
> +	if (err) {
> +		*value = 0;
> +		return err;
> +	}
> +
> +	/* Treat the PCI all-ones value as a missing device. */
> +	if (PCI_POSSIBLE_ERROR(*value)) {
> +		*value = 0;
> +		return -ENODEV;
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(hygon_smn_read);
> +
> +int hygon_smn_write(u16 node, u32 address, u32 value)
> +{
> +	return smn_rw(node, address, &value, true);
> +}
> +EXPORT_SYMBOL_GPL(hygon_smn_write);
> +

Same point as before for exported functions.

Overall, looks good to me.

Reviewed-by: Yazen Ghannam <yazen.ghannam@amd.com>

Thanks,
Yazen


      reply	other threads:[~2026-10-17 15:41 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-09  9:01 [PATCH v7 0/7] x86/hygon: Add Family 0x18 DF node enumeration and " Lin Wang
2026-10-09  9:01 ` [PATCH v7 1/7] x86/hygon: Add Family 0x18 DF node enumeration Lin Wang
2026-10-17 14:52   ` Yazen Ghannam
2026-10-09  9:01 ` [PATCH v7 2/7] x86/hygon: Map CPU NodeIds to DF nodes Lin Wang
2026-10-17 15:10   ` Yazen Ghannam
2026-10-09  9:01 ` [PATCH v7 3/7] EDAC/amd64: Stop matching Hygon CPUs Lin Wang
2026-10-17 15:16   ` Yazen Ghannam
2026-10-09  9:01 ` [PATCH v7 4/7] RAS/AMD/ATL: Match AMD CPUs only Lin Wang
2026-10-17 15:20   ` Yazen Ghannam
2026-10-09  9:01 ` [PATCH v7 5/7] x86/amd_nb: Restrict the northbridge framework to AMD CPUs Lin Wang
2026-10-09  9:01 ` [PATCH v7 6/7] x86/amd_node: Restrict SMN setup " Lin Wang
2026-10-17 15:33   ` Yazen Ghannam
2026-10-09  9:01 ` [PATCH v7 7/7] x86/hygon: Add Family 0x18 SMN access Lin Wang
2026-10-17 15:41   ` Yazen Ghannam [this message]

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=20261017154140.GF359037@yaz-khff2.amd.com \
    --to=yazen.ghannam@amd.com \
    --cc=bp@alien8.de \
    --cc=dave.hansen@linux.intel.com \
    --cc=hpa@zytor.com \
    --cc=linux-edac@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mario.limonciello@amd.com \
    --cc=mingo@redhat.com \
    --cc=puwen@hygon.cn \
    --cc=tglx@kernel.org \
    --cc=tony.luck@intel.com \
    --cc=wanglin@open-hieco.net \
    --cc=x86@kernel.org \
    /path/to/YOUR_REPLY

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

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®