Skip to contentSkip to conversation

Add ACPI-based PMT discovery support for Intel PMC

PATCH Superseded David E. Box 2 participants 18 messages Jun 11 – Jun 12

Subsystems (4)

v7 of 8 Superseded Latest is v8 →

lore
Share & tools
lore

David E. Box

author maintainer
#0 lore raw

v7 cover Add ACPI-based PMT discovery support for Intel PMC

This series adds ACPI-based PMT discovery support and wires it into the Intel PMC telemetry stack for Nova Lake S.

Today, PMT discovery flow is primarily PCI-oriented. Some platforms expose PMT discovery via ACPI _DSD, and PMC telemetry needs to consume that path for both standalone and companion-device cases. This series addresses that in three logical steps:

1. Prepare intel_vsec for ACPI-backed discovery
2. Refactor PMT class handling so discovery source is abstracted (PCI or ACPI)
3. Add/enable PMC consumers using ACPI discovery, including NVL SSRAM support

Patches for step 1 were previously accepted. V4 onward includes the remaining integration patches.

Testing:
Tested on Nova Lake S system and confirmed enumeration from both PCI and ACPI enumerated PCD and PCH telemetry endpoints. Existing telemetry from NVL CPU and an add-on Battlemage card also continued to work.

---
V7 changes:
- Addressed Ilpo Järvinen's review feedback:
- Patch 05/15: switched min_t(size_t, ...) to min() for size_t operands.
- Patch 06/15: aligned local assignment style and kept min() usage in header copy sizing.
- Patch 08/15: fixed ACPI DISC_TBL offset parsing to preserve masked (unshifted) encoding consistent with DVSEC handling.
- Patch 10/15: simplified pmc_ssram_telemetry_pci_init() tail return to return 0.
- Patch 12/15: made PMC_SSRAM_UNPROBED explicitly = 0 and replaced barrier pairing with smp_store_release()/smp_load_acquire().
- No patch reordering, squashing, or drops from v6.

V6 changes:
- Squashed Xi Pardee's two patches (v5 patches 10 and 13) into a single patch (v6 patch 12). The two patches logically belonged together as they both addressed per-index probe state tracking and reentrant probe protection.
- Reordered v5 patch 12 ("Add PCI platform data") to v6 patch 10 to maintain better logical flow before the squashed probe state patch.
- Address other style and cleanup comments.

V5 changes:
- Addressed Ilpo Järvinen's review feedback on patches 08 and 14:
- Added #include <linux/limits.h> for U16_MAX in pwrm_telemetry.c.
- Split acpi_handle declaration from ACPI_HANDLE() assignment in both pmc_pwrm_acpi_probe() and pmc_ssram_telemetry_acpi_init(). The assignment now sits immediately above its !handle check.
- Reordered local variables in those two functions in reverse-xmas-tree order.
- Patch 14: fixed a __free(pmc_acpi_free) ordering bug that caused buf.pointer to leak. The cleanup variable is now declared after acpi_evaluate_object() populates buf.pointer, and the GUID search operates on the cleanup variable so its current value tracks the actual allocation.
- Patches 05 and 06: bounded the new discovery-header memcpy_fromio() to resource_size() of the mapped DVSEC entry. The cache introduced in patch 05 (and the unified PCI header fetch in patch 06) would otherwise read 16 bytes from namespaces whose DVSEC entry_size is only 3 dwords (telemetry).
- Patch 06: documented in the ACPI branch that entry->disc_table = NULL is intentional, so consumers that dereference disc_table must only be wired to INTEL_VSEC_DISC_PCI namespaces.
- Remaining patches unchanged from v4.

V4 changes:
- Bound PMT discovery-header sizing to shared canonical definitions:
- Added INTEL_VSEC_ACPI_DISC_DWORDS in intel_vsec API.
- Added PMT_DISC_HEADER_DWORDS in PMT class, derived from the shared definition.
- Replaced literal header copy sizes in PMT header fetch with size bound

    to entry->disc_header.

- Updated PMC ACPI discovery typing to use shared acpi_disc_t consistently:
- acpi_disc_t now references INTEL_VSEC_ACPI_DISC_DWORDS.
- pmc_parse_telem_dsd() uses acpi_disc_t return type and declaration-site allocation with __free(kfree).
- Included intel_vsec header in PMC core declarations to align type ownership.
- In SSRAM ACPI scaffolding, switched temporary discovery pointer from raw u32 (*)[4] to acpi_disc_t for consistency with exported PMC helper types.
- Remaining patches unchanged from v3.

V3 changes:
- Dropped "platform/x86/intel/pmt: Move header decode into common helper"
- The dropped patch caused a crashlog regression because disc_res was accessed before assignment
- Updated the surrounding PMT patches to keep the needed ACPI header-fetch work without the dropped helper
- Added "platform/x86/intel/pmt: Cache the telemetry discovery header" to carry the post-v3 bug fix cleanly
- Dropped the standalone cleanup-pattern patch to keep the simpler ssram pointer flow requested in review

David E. Box (14):
platform/x86/intel/pmt: Add pre/post decode hooks around header
parsing
platform/x86/intel/pmt/crashlog: Split init into pre-decode platform/x86/intel/pmt/telemetry: Move overlap check to post-decode
hook
platform/x86/intel/pmt: Pass discovery index instead of resource platform/x86/intel/pmt: Cache the telemetry discovery header platform/x86/intel/pmt: Unify header fetch and add ACPI source platform/x86/intel/pmc: Add PMC SSRAM Kconfig description platform/x86/intel/pmc: Add ACPI PWRM telemetry driver for Nova Lake S platform/x86/intel/pmc/ssram: Rename probe and PCI ID table for
consistency
platform/x86/intel/pmc/ssram: Add PCI platform data platform/x86/intel/pmc/ssram: Refactor DEVID/PWRMBASE extraction into
helper
platform/x86/intel/pmc/ssram: Add ACPI discovery scaffolding platform/x86/intel/pmc/ssram: Make PMT registration optional platform/x86/intel/pmc: Add NVL PCI IDs for SSRAM telemetry discovery

Xi Pardee (1):
platform/x86/intel/pmc/ssram: Switch to static array with per-index
probe state

 drivers/platform/x86/intel/pmc/Kconfig        |  25 ++
 drivers/platform/x86/intel/pmc/Makefile       |   2 +
 drivers/platform/x86/intel/pmc/core.h         |  16 +
 .../platform/x86/intel/pmc/pwrm_telemetry.c   | 216 ++++++++++
 .../platform/x86/intel/pmc/ssram_telemetry.c  | 380 +++++++++++++++---
 drivers/platform/x86/intel/pmt/class.c        | 156 ++++++-
 drivers/platform/x86/intel/pmt/class.h        |   6 +
 drivers/platform/x86/intel/pmt/crashlog.c     |  19 +-
 drivers/platform/x86/intel/pmt/telemetry.c    |  34 +-
 include/linux/intel_vsec.h                    |   5 +-
 10 files changed, 770 insertions(+), 89 deletions(-)
 create mode 100644 drivers/platform/x86/intel/pmc/pwrm_telemetry.c

base-commit: 2565a28cdcdcb035e151d285efcba26bccb3726e

Signature · 1 line
2.43.0
author maintainer
#1 lore raw

PATCH v7 1/15 platform/x86/intel/pmt: Add pre/post decode hooks around header parsing

Add optional pre- and post-decode callbacks to the PMT class so namespaces can perform setup and cleanup steps around header parsing.

- Add pmt_pre_decode() and pmt_post_decode() to struct intel_pmt_namespace.
- Update intel_pmt_dev_create() to invoke, in order:
pre → header_decode() → post.
- Keep the existing pmt_header_decode() callback unchanged.

No functional changes. This adds flexibility for upcoming decoders while preserving current behavior.

---
V7 - No changes

V6 - No changes

V5 - No changes

V4 - No changes

V3 - No changes

V2 - No changes

 drivers/platform/x86/intel/pmt/class.c | 12 ++++++++++++
 drivers/platform/x86/intel/pmt/class.h |  4 ++++
 2 files changed, 16 insertions(+)
drivers/platform/x86/intel/pmt/class.c +12 -0
index b4c9964df807..9b315334a69b 100644
@@ -381,10 +381,22 @@ int intel_pmt_dev_create(struct intel_pmt_entry *entry, struct intel_pmt_namespa
 	if (IS_ERR(entry->disc_table))
 		return PTR_ERR(entry->disc_table);
 
+	if (ns->pmt_pre_decode) {
+		ret = ns->pmt_pre_decode(intel_vsec_dev, entry);
+		if (ret)
+			return ret;
+	}
+
 	ret = ns->pmt_header_decode(entry, dev);
 	if (ret)
 		return ret;
 
+	if (ns->pmt_post_decode) {
+		ret = ns->pmt_post_decode(intel_vsec_dev, entry);
+		if (ret)
+			return ret;
+	}
+
 	ret = intel_pmt_populate_entry(entry, intel_vsec_dev, disc_res);
 	if (ret)
 		return ret;
drivers/platform/x86/intel/pmt/class.h +4 -0
index 1ae56a5baad2..ff39014b208c 100644
@@ -62,6 +62,10 @@ struct intel_pmt_namespace {
 	struct xarray *xa;
 	int (*pmt_header_decode)(struct intel_pmt_entry *entry,
 				 struct device *dev);
+	int (*pmt_pre_decode)(struct intel_vsec_device *ivdev,
+			      struct intel_pmt_entry *entry);
+	int (*pmt_post_decode)(struct intel_vsec_device *ivdev,
+			       struct intel_pmt_entry *entry);
 	int (*pmt_add_endpoint)(struct intel_vsec_device *ivdev,
 				struct intel_pmt_entry *entry);
 };
Signature · 1 line
2.43.0
author maintainer
#2 lore raw

PATCH v7 2/15 platform/x86/intel/pmt/crashlog: Split init into pre-decode

In reply to David E. Box

Refactor crashlog initialization to use the PMT namespace pre-decode hook:

- Add pmt_crashlog_pre_decode() to parse type/version, select the

    crashlog_info, initialize the control mutex, and set entry->attr_grp.

- Simplify pmt_crashlog_header_decode() to only read header fields from the discovery table.
- Wire the namespace with .pmt_pre_decode = pmt_crashlog_pre_decode.

This separates structural initialization from header parsing, aligning crashlog with the PMT class pre/post decode flow.

---
V7 - No changes

V6 - No changes

V5 - No changes

V4 - No changes

V3 - No changes

V2 - No changes

 drivers/platform/x86/intel/pmt/crashlog.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)
drivers/platform/x86/intel/pmt/crashlog.c +13 -6
index b0393c9c5b4b..f936daf99e4d 100644
@@ -496,11 +496,9 @@ static const struct crashlog_info *select_crashlog_info(u32 type, u32 version)
 	return &crashlog_type1_ver2;
 }
 
-static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
-				      struct device *dev)
+static int pmt_crashlog_pre_decode(struct intel_vsec_device *ivdev,
+				   struct intel_pmt_entry *entry)
 {
-	void __iomem *disc_table = entry->disc_table;
-	struct intel_pmt_header *header = &entry->header;
 	struct crashlog_entry *crashlog;
 	u32 version;
 	u32 type;
@@ -513,6 +511,16 @@ static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
 	mutex_init(&crashlog->control_mutex);
 
 	crashlog->info = select_crashlog_info(type, version);
+	entry->attr_grp = crashlog->info->attr_grp;
+
+	return 0;
+}
+
+static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
+				      struct device *dev)
+{
+	void __iomem *disc_table = entry->disc_table;
+	struct intel_pmt_header *header = &entry->header;
 
 	header->access_type = GET_ACCESS(readl(disc_table));
 	header->guid = readl(disc_table + GUID_OFFSET);
@@ -521,8 +529,6 @@ static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
 	/* Size is measured in DWORDS, but accessor returns bytes */
 	header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET));
 
-	entry->attr_grp = crashlog->info->attr_grp;
-
 	return 0;
 }
 
@@ -530,6 +536,7 @@ static DEFINE_XARRAY_ALLOC(crashlog_array);
 static struct intel_pmt_namespace pmt_crashlog_ns = {
 	.name = "crashlog",
 	.xa = &crashlog_array,
+	.pmt_pre_decode = pmt_crashlog_pre_decode,
 	.pmt_header_decode = pmt_crashlog_header_decode,
 };
 
Signature · 1 line
2.43.0
author maintainer
#3 lore raw

PATCH v7 3/15 platform/x86/intel/pmt/telemetry: Move overlap check to post-decode hook

In reply to David E. Box

Update the telemetry namespace to use the new PMT class pre/post decode interface. The overlap check, which previously occurred during header decode, is now performed in the post-decode hook once header fields are populated. This preserves existing behavior while reusing the same header decode logic across PMT drivers.

---
V7 - No changes

V6 - No changes

V5 - No changes

V4 - No changes

V3 - No changes

V2 - No changes

 drivers/platform/x86/intel/pmt/class.h     |  1 +
 drivers/platform/x86/intel/pmt/telemetry.c | 24 ++++++++++++++--------
 2 files changed, 16 insertions(+), 9 deletions(-)
drivers/platform/x86/intel/pmt/class.h +1 -0
index ff39014b208c..8a0db0ef58c1 100644
@@ -37,6 +37,7 @@ struct intel_pmt_header {
 	u32	size;
 	u32	guid;
 	u8	access_type;
+	u8	telem_type;
 };
 
 struct intel_pmt_entry {
drivers/platform/x86/intel/pmt/telemetry.c +15 -9
index bdc7c24a3678..d22f633638be 100644
@@ -58,14 +58,9 @@ struct pmt_telem_priv {
 	struct intel_pmt_entry		entry[];
 };
 
-static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry,
-				      struct device *dev)
+static bool pmt_telem_region_overlaps(struct device *dev, u32 guid, u32 type)
 {
-	u32 guid = readl(entry->disc_table + TELEM_GUID_OFFSET);
-
 	if (intel_pmt_is_early_client_hw(dev)) {
-		u32 type = TELEM_TYPE(readl(entry->disc_table));
-
 		if ((type == TELEM_TYPE_PUNIT_FIXED) ||
 		    (guid == TELEM_CLIENT_FIXED_BLOCK_GUID))
 			return true;
@@ -80,15 +75,25 @@ static int pmt_telem_header_decode(struct intel_pmt_entry *entry,
 	void __iomem *disc_table = entry->disc_table;
 	struct intel_pmt_header *header = &entry->header;
 
-	if (pmt_telem_region_overlaps(entry, dev))
-		return 1;
-
 	header->access_type = TELEM_ACCESS(readl(disc_table));
 	header->guid = readl(disc_table + TELEM_GUID_OFFSET);
 	header->base_offset = readl(disc_table + TELEM_BASE_OFFSET);
 
 	/* Size is measured in DWORDS, but accessor returns bytes */
 	header->size = TELEM_SIZE(readl(disc_table));
+	header->telem_type = TELEM_TYPE(readl(entry->disc_table));
+
+	return 0;
+}
+
+static int pmt_telem_post_decode(struct intel_vsec_device *ivdev,
+				 struct intel_pmt_entry *entry)
+{
+	struct intel_pmt_header *header = &entry->header;
+	struct device *dev = &ivdev->auxdev.dev;
+
+	if (pmt_telem_region_overlaps(dev, header->guid, header->telem_type))
+		return 1;
 
 	/*
 	 * Some devices may expose non-functioning entries that are
@@ -131,6 +136,7 @@ static struct intel_pmt_namespace pmt_telem_ns = {
 	.name = "telem",
 	.xa = &telem_array,
 	.pmt_header_decode = pmt_telem_header_decode,
+	.pmt_post_decode = pmt_telem_post_decode,
 	.pmt_add_endpoint = pmt_telem_add_endpoint,
 };
 
Signature · 1 line
2.43.0
author maintainer
#4 lore raw

PATCH v7 4/15 platform/x86/intel/pmt: Pass discovery index instead of resource

In reply to David E. Box

Change PMT class code to pass a discovery index rather than a direct struct resource when creating entries. This allows the class to identify the discovery source generically without assuming PCI BAR resources. For PCI devices, the index still resolves to a resource in the intel_vsec_device. Other discovery sources, such as ACPI, can use the same index without needing a struct resource.

---
V7 - No changes

V6 - No changes

V5 - No changes

V4 - No changes

V3 changes:
- Rebased after dropping the previous "Move header decode into common helper" patch
- Adjusted the intel_pmt_populate_entry() call path to match the restored intel_pmt_dev_create() flow
- Did not apply Ilpo V2 signoff due to these changes.

V2 - No changes

 drivers/platform/x86/intel/pmt/class.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
drivers/platform/x86/intel/pmt/class.c +4 -2
index 9b315334a69b..7da8279b54f8 100644
@@ -206,11 +206,12 @@ EXPORT_SYMBOL_GPL(intel_pmt_class);
 
 static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
 				    struct intel_vsec_device *ivdev,
-				    struct resource *disc_res)
+				    int idx)
 {
 	struct pci_dev *pci_dev = to_pci_dev(ivdev->dev);
 	struct device *dev = &ivdev->auxdev.dev;
 	struct intel_pmt_header *header = &entry->header;
+	struct resource *disc_res;
 	u8 bir;
 
 	/*
@@ -235,6 +236,7 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
 		 * For access_type LOCAL, the base address is as follows:
 		 * base address = end of discovery region + base offset
 		 */
+		disc_res = &ivdev->resource[idx];
 		entry->base_addr = disc_res->end + 1 + header->base_offset;
 
 		/*
@@ -397,7 +399,7 @@ int intel_pmt_dev_create(struct intel_pmt_entry *entry, struct intel_pmt_namespa
 			return ret;
 	}
 
-	ret = intel_pmt_populate_entry(entry, intel_vsec_dev, disc_res);
+	ret = intel_pmt_populate_entry(entry, intel_vsec_dev, idx);
 	if (ret)
 		return ret;
 
Signature · 1 line
2.43.0
author maintainer
#5 lore raw

PATCH v7 5/15 platform/x86/intel/pmt: Cache the telemetry discovery header

In reply to David E. Box

pmt_telem_header_decode() only needs the discovery header dwords, but it currently decodes them by reading directly from entry->disc_table.

Cache the discovery header in intel_pmt_entry when the device is created and have telemetry decode use the cached values instead of performing MMIO reads at decode time.

The DVSEC discovery resource for a namespace is sized by its per-entry entry_size (in dwords), which can be less than the 4-dword cache (e.g. telemetry uses entry_size = 3, i.e. 12 bytes). Cap the memcpy_fromio() to resource_size(disc_res) so the new cache does not read past the mapped region. Any unread dwords stay zero from the zero-initialized allocation of the containing struct.

This keeps the telemetry header decode path independent of how the discovery data is backed and avoids baking a direct MMIO assumption into the feature-specific decode logic.

Assisted-by GitHub-Copilot:claude-opus-4.7

---
V7 changes:
- Replaced min_t(size_t, ...) with min() because both operands are size_t

    (Ilpo).

V6 changes:
- Added #include <linux/minmax.h> for min_t() macro used in memcpy_fromio() size calculation (Ilpo).

V5 changes:
- Cap memcpy_fromio() of the cached discovery header to resource_size(disc_res) so the newly introduced cache does not over-read namespaces whose DVSEC entry_size is smaller than the cache (e.g. telemetry has entry_size = 3, 12 bytes).

V4 - No changes

V3 changes:
- New patch split out from PMT header-fetch rework to cache discovery header data before downstream decode/population.
- Added to carry the post-v3 bug fix while preserving the original series ordering intent.

 drivers/platform/x86/intel/pmt/class.c     | 11 +++++++++++
 drivers/platform/x86/intel/pmt/class.h     |  1 +
 drivers/platform/x86/intel/pmt/telemetry.c | 12 ++++++------
 3 files changed, 18 insertions(+), 6 deletions(-)
drivers/platform/x86/intel/pmt/class.c +11 -0
index 7da8279b54f8..8d27f59d5bff 100644
@@ -12,6 +12,7 @@
 #include <linux/log2.h>
 #include <linux/intel_vsec.h>
 #include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/minmax.h>
 #include <linux/module.h>
 #include <linux/mm.h>
 #include <linux/pci.h>
@@ -383,6 +384,16 @@ int intel_pmt_dev_create(struct intel_pmt_entry *entry, struct intel_pmt_namespa
 	if (IS_ERR(entry->disc_table))
 		return PTR_ERR(entry->disc_table);
 
+	/*
+	 * The mapped discovery resource may be smaller than disc_header (its
+	 * size is the namespace's DVSEC entry_size in dwords, which can be
+	 * less than 4). Cap the copy to the actual resource size to avoid
+	 * reading past the mapped region; any unread dwords stay zero from
+	 * the zero-initialized allocation of the containing struct.
+	 */
+	memcpy_fromio(entry->disc_header, entry->disc_table,
+		      min(sizeof(entry->disc_header), resource_size(disc_res)));
+
 	if (ns->pmt_pre_decode) {
 		ret = ns->pmt_pre_decode(intel_vsec_dev, entry);
 		if (ret)
drivers/platform/x86/intel/pmt/class.h +1 -0
index 8a0db0ef58c1..84202fc7920c 100644
@@ -44,6 +44,7 @@ struct intel_pmt_entry {
 	struct telem_endpoint	*ep;
 	struct pci_dev		*pcidev;
 	struct intel_pmt_header	header;
+	u32			disc_header[4];
 	struct bin_attribute	pmt_bin_attr;
 	const struct attribute_group *attr_grp;
 	struct kobject		*kobj;
drivers/platform/x86/intel/pmt/telemetry.c +6 -6
index d22f633638be..953f35b6daec 100644
@@ -72,16 +72,16 @@ static bool pmt_telem_region_overlaps(struct device *dev, u32 guid, u32 type)
 static int pmt_telem_header_decode(struct intel_pmt_entry *entry,
 				   struct device *dev)
 {
-	void __iomem *disc_table = entry->disc_table;
 	struct intel_pmt_header *header = &entry->header;
+	u32 *disc_header = entry->disc_header;
 
-	header->access_type = TELEM_ACCESS(readl(disc_table));
-	header->guid = readl(disc_table + TELEM_GUID_OFFSET);
-	header->base_offset = readl(disc_table + TELEM_BASE_OFFSET);
+	header->access_type = TELEM_ACCESS(disc_header[0]);
+	header->guid = disc_header[1];
+	header->base_offset = disc_header[2];
 
 	/* Size is measured in DWORDS, but accessor returns bytes */
-	header->size = TELEM_SIZE(readl(disc_table));
-	header->telem_type = TELEM_TYPE(readl(entry->disc_table));
+	header->size = TELEM_SIZE(disc_header[0]);
+	header->telem_type = TELEM_TYPE(disc_header[0]);
 
 	return 0;
 }
Signature · 1 line
2.43.0
author maintainer
#6 lore raw

PATCH v7 6/15 platform/x86/intel/pmt: Unify header fetch and add ACPI source

In reply to David E. Box

Allow the PMT class to read discovery headers from either PCI MMIO or ACPI-provided entries, depending on the discovery source. The new source-aware fetch helper caches the canonical discovery header for both paths, capping PCI MMIO reads to the mapped resource size, while keeping the mapped PCI discovery table available for users such as crashlog.

Split intel_pmt_populate_entry() into source-specific resolvers:
- pmt_resolve_access_pci(): handles both ACCESS_LOCAL and ACCESS_BARID

    for PCI-backed devices and sets entry->pcidev. Same existing
    functionality.

- pmt_resolve_access_acpi(): handles only ACCESS_BARID for ACPI-backed devices, rejecting ACCESS_LOCAL which has no valid semantics without a physical discovery resource.

This maintains existing PCI behavior and makes no functional changes for PCI devices.

Assisted-by GitHub-Copilot:claude-opus-4.7

---
V7 changes:
- Updated header copy size handling to use min() for size_t operands and aligned local style per review feedback (Ilpo).

V6 changes:
- Removed unneeded headers parameter from pmt_get_headers() (Ilpo).
- Added memset() to zero-initialize disc_header before copying to avoid uninitialized data in entry reuse scenarios (Ilpo/Sashiko).

V5 changes:
- Bounded memcpy_fromio() to resource_size() of the mapped DVSEC entry to avoid over-reading namespaces with smaller entry_size.
- Documented that entry->disc_table = NULL in the ACPI branch is intentional and that consumers must only be wired to INTEL_VSEC_DISC_PCI namespaces if they dereference disc_table.

V4 - Replaced local raw ACPI discovery pointer type u32 (*)[4] with
acpi_disc_t for consistency with exported PMC helper types.

V3 - No changes

V2 changes:
- Replaced intermediate header-decode helper with inline ACPI path
- Removed separate disc_acpi resource
- Moved ACPI fetch directly into intel_pmt_dev_create

 drivers/platform/x86/intel/pmt/class.c | 151 +++++++++++++++++++++----
 drivers/platform/x86/intel/pmt/class.h |   2 +-
 include/linux/intel_vsec.h             |   5 +-
 3 files changed, 136 insertions(+), 22 deletions(-)
drivers/platform/x86/intel/pmt/class.c +132 -19
index 8d27f59d5bff..d0ab8e33c62a 100644
@@ -205,9 +205,9 @@ struct class intel_pmt_class = {
 };
 EXPORT_SYMBOL_GPL(intel_pmt_class);
 
-static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
-				    struct intel_vsec_device *ivdev,
-				    int idx)
+static int pmt_resolve_access_pci(struct intel_pmt_entry *entry,
+				  struct intel_vsec_device *ivdev,
+				  int idx)
 {
 	struct pci_dev *pci_dev = to_pci_dev(ivdev->dev);
 	struct device *dev = &ivdev->auxdev.dev;
@@ -287,6 +287,81 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
 	}
 
 	entry->pcidev = pci_dev;
+
+	return 0;
+}
+
+static int pmt_resolve_access_acpi(struct intel_pmt_entry *entry,
+				   struct intel_vsec_device *ivdev)
+{
+	struct pci_dev *pci_dev = NULL;
+	struct device *dev = &ivdev->auxdev.dev;
+	struct intel_pmt_header *header = &entry->header;
+	u8 bir;
+
+	if (dev_is_pci(ivdev->dev))
+		pci_dev = to_pci_dev(ivdev->dev);
+
+	/*
+	 * The base offset should always be 8 byte aligned.
+	 *
+	 * For non-local access types the lower 3 bits of base offset
+	 * contains the index of the base address register where the
+	 * telemetry can be found.
+	 */
+	bir = GET_BIR(header->base_offset);
+
+	switch (header->access_type) {
+	case ACCESS_BARID:
+		/* ACPI platform drivers use base_addr */
+		if (ivdev->base_addr) {
+			entry->base_addr = ivdev->base_addr +
+					   GET_ADDRESS(header->base_offset);
+			break;
+		}
+
+		/* If base_addr is not provided, then this is an ACPI companion device */
+		if (!pci_dev) {
+			dev_err(dev, "ACCESS_BARID requires PCI BAR resources or base_addr\n");
+			return -EINVAL;
+		}
+
+		entry->base_addr = pci_resource_start(pci_dev, bir) +
+				   GET_ADDRESS(header->base_offset);
+		break;
+	default:
+		dev_err(dev, "Unsupported access type %d for ACPI based PMT\n",
+			header->access_type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
+				    struct intel_vsec_device *ivdev,
+				    int idx)
+{
+	struct intel_pmt_header *header = &entry->header;
+	struct device *dev = &ivdev->auxdev.dev;
+	int ret;
+
+	switch (ivdev->src) {
+	case INTEL_VSEC_DISC_PCI:
+		ret = pmt_resolve_access_pci(entry, ivdev, idx);
+		if (ret)
+			return ret;
+		break;
+	case INTEL_VSEC_DISC_ACPI:
+		ret = pmt_resolve_access_acpi(entry, ivdev);
+		if (ret)
+			return ret;
+		break;
+	default:
+		dev_err(dev, "Unknown discovery source: %d\n", ivdev->src);
+		return -EINVAL;
+	}
+
 	entry->guid = header->guid;
 	entry->size = header->size;
 	entry->cb = ivdev->priv_data;
@@ -371,28 +446,66 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
 	return ret;
 }
 
+static int pmt_get_headers(struct intel_vsec_device *ivdev, int idx,
+			   struct intel_pmt_entry *entry)
+{
+	struct device *dev = &ivdev->auxdev.dev;
+	size_t header_bytes = sizeof(entry->disc_header);
+
+	switch (ivdev->src) {
+	case INTEL_VSEC_DISC_PCI: {
+		struct resource *disc_res = &ivdev->resource[idx];
+		void __iomem *disc_table;
+
+		disc_table = devm_ioremap_resource(dev, disc_res);
+		if (IS_ERR(disc_table))
+			return PTR_ERR(disc_table);
+
+		/*
+		 * The mapped resource is sized by the namespace's DVSEC
+		 * entry_size (in dwords), which can be less than the default
+		 * size (e.g. telemetry uses entry_size = 3, 12 bytes). Cap the
+		 * copy to resource_size() to avoid reading past the mapped
+		 * region.
+		 */
+		memset(entry->disc_header, 0, header_bytes);
+		memcpy_fromio(entry->disc_header, disc_table,
+			      min(header_bytes, resource_size(disc_res)));
+
+		/* Used by crashlog driver */
+		entry->disc_table = disc_table;
+
+		return 0;
+	}
+	case INTEL_VSEC_DISC_ACPI: {
+		memcpy(entry->disc_header, &ivdev->acpi_disc[idx][0], header_bytes);
+		/*
+		 * No MMIO mapping exists on the ACPI source path; the cached
+		 * headers are the only view of the discovery record. Consumers
+		 * that dereference disc_table (e.g. crashlog) must therefore
+		 * only be wired to namespaces backed by INTEL_VSEC_DISC_PCI.
+		 */
+		entry->disc_table = NULL;
+
+		return 0;
+	}
+	default:
+		dev_err(dev, "Unknown discovery source type: %d\n", ivdev->src);
+		break;
+	}
+
+	return -EINVAL;
+}
+
 int intel_pmt_dev_create(struct intel_pmt_entry *entry, struct intel_pmt_namespace *ns,
 			 struct intel_vsec_device *intel_vsec_dev, int idx)
 {
 	struct device *dev = &intel_vsec_dev->auxdev.dev;
-	struct resource	*disc_res;
 	int ret;
 
-	disc_res = &intel_vsec_dev->resource[idx];
-
-	entry->disc_table = devm_ioremap_resource(dev, disc_res);
-	if (IS_ERR(entry->disc_table))
-		return PTR_ERR(entry->disc_table);
-
-	/*
-	 * The mapped discovery resource may be smaller than disc_header (its
-	 * size is the namespace's DVSEC entry_size in dwords, which can be
-	 * less than 4). Cap the copy to the actual resource size to avoid
-	 * reading past the mapped region; any unread dwords stay zero from
-	 * the zero-initialized allocation of the containing struct.
-	 */
-	memcpy_fromio(entry->disc_header, entry->disc_table,
-		      min(sizeof(entry->disc_header), resource_size(disc_res)));
+	ret = pmt_get_headers(intel_vsec_dev, idx, entry);
+	if (ret)
+		return ret;
 
 	if (ns->pmt_pre_decode) {
 		ret = ns->pmt_pre_decode(intel_vsec_dev, entry);
drivers/platform/x86/intel/pmt/class.h +1 -1
index 84202fc7920c..a0ece4fc3837 100644
@@ -44,7 +44,7 @@ struct intel_pmt_entry {
 	struct telem_endpoint	*ep;
 	struct pci_dev		*pcidev;
 	struct intel_pmt_header	header;
-	u32			disc_header[4];
+	u32			disc_header[PMT_DISC_DWORDS];
 	struct bin_attribute	pmt_bin_attr;
 	const struct attribute_group *attr_grp;
 	struct kobject		*kobj;
include/linux/intel_vsec.h +3 -2
index 07ea563f524e..843cda8f8644 100644
@@ -28,6 +28,7 @@
 #define INTEL_DVSEC_TABLE_BAR(x)	((x) & GENMASK(2, 0))
 #define INTEL_DVSEC_TABLE_OFFSET(x)	((x) & GENMASK(31, 3))
 #define TABLE_OFFSET_SHIFT		3
+#define PMT_DISC_DWORDS		4
 
 struct device;
 struct pci_dev;
@@ -122,7 +123,7 @@ struct intel_vsec_platform_info {
 	struct device *parent;
 	struct intel_vsec_header **headers;
 	const struct vsec_feature_dependency *deps;
-	u32 (*acpi_disc)[4];
+	u32 (*acpi_disc)[PMT_DISC_DWORDS];
 	enum intel_vsec_disc_source src;
 	void *priv_data;
 	unsigned long caps;
@@ -153,7 +154,7 @@ struct intel_vsec_platform_info {
 struct intel_vsec_device {
 	struct auxiliary_device auxdev;
 	struct device *dev;
-	u32 (*acpi_disc)[4];
+	u32 (*acpi_disc)[PMT_DISC_DWORDS];
 	enum intel_vsec_disc_source src;
 	struct ida *ida;
 	int num_resources;
Signature · 1 line
2.43.0
author maintainer
#7 lore raw

PATCH v7 7/15 platform/x86/intel/pmc: Add PMC SSRAM Kconfig description

In reply to David E. Box

Add a proper description for the intel_pmc_ssram driver.

---
V7 - No changes

V6 - No changes

V5 - No changes

V4 - No changes

V3 - No changes

V2 - No changes

 drivers/platform/x86/intel/pmc/Kconfig | 11 +++++++++++
 1 file changed, 11 insertions(+)
drivers/platform/x86/intel/pmc/Kconfig +11 -0
index c6ef0bcf76af..e9012b703918 100644
@@ -28,3 +28,14 @@ config INTEL_PMC_CORE
 
 config INTEL_PMC_SSRAM_TELEMETRY
 	tristate
+	help
+	  This driver discovers PMC SSRAM telemetry regions through the PMC's
+	  MMIO interface (PCI) or ACPI _DSD properties and registers them with
+	  the Intel VSEC framework as Intel PMT telemetry devices.
+
+	  It probes the PMC SSRAM device, extracts DVSEC information from MMIO,
+	  reads device IDs and base addresses for multiple PMCs (main, IOE, PCH),
+	  and exposes the discovered telemetry through Intel PMT interfaces
+	  (including sysfs).
+
+	  This option is selected by INTEL_PMC_CORE.
Signature · 1 line
2.43.0
author maintainer
#8 lore raw

PATCH v7 8/15 platform/x86/intel/pmc: Add ACPI PWRM telemetry driver for Nova Lake S

In reply to David E. Box

Add an ACPI-based PMC PWRM telemetry driver for Nova Lake S. The driver locates PMT discovery data in _DSD under the Intel VSEC UUID, parses it, and registers telemetry regions with the PMT/VSEC framework so PMC telemetry is exposed via existing PMT interfaces.

Export pmc_parse_telem_dsd() and pmc_find_telem_guid() to support ACPI discovery in other PMC drivers (e.g., ssram_telemetry) without duplicating ACPI parsing logic. Also export acpi_disc_t typedef from core.h for callers to properly declare discovery table arrays.

Selected by INTEL_PMC_CORE. Existing PCI functionality is preserved.

Assisted-by GitHub-Copilot:claude-opus-4.7

---
V7 changes:
- Fixed ACPI discovery offset parsing to preserve the masked (unshifted) DISC_TBL encoding instead of using FIELD_GET() right-shift semantics (Ilpo).

V6 - No changes

V5 changes:
- Added #include <linux/limits.h> for U16_MAX (Ilpo).
- Split acpi_handle declaration from ACPI_HANDLE() assignment and placed the assignment immediately before the !handle check (Ilpo).
- Reordered local variables in pmc_pwrm_acpi_probe() in reverse-xmas-tree order (Ilpo).

V4 changes:
- These changes were supposed to be in V3
- Updated pmc_parse_telem_dsd() in pwrm_telemetry.c to use acpi_disc_t in the function return type for consistency with the exported typedef
- In pmc_parse_telem_dsd(), change acpi_disc declaration to happen at the allocation site as specified by cleanup.h
- Style, readability and cleanup-path refinement based on review feedback

V2 changes:
- Added explicit <linux/uuid.h> include for guid_t type availability in core.h
- Added explicit <linux/bits.h> include in pwrm_telemetry.c for GENMASK()
- Added <linux/cleanup.h> and converted goto based cleanup to __free() attributes per Ilpo's feedback
- Combined u64 hdr0 and u64 hdr1 into single declaration
- Converted pmc_parse_telem_dsd() to return acpi_disc directly with ERR_PTR() for failures
- Added braces around _DSD evaluation failure path

 drivers/platform/x86/intel/pmc/Kconfig        |  14 ++
 drivers/platform/x86/intel/pmc/Makefile       |   2 +
 drivers/platform/x86/intel/pmc/core.h         |  16 ++
 .../platform/x86/intel/pmc/pwrm_telemetry.c   | 216 ++++++++++++++++++
 4 files changed, 248 insertions(+)
 create mode 100644 drivers/platform/x86/intel/pmc/pwrm_telemetry.c
drivers/platform/x86/intel/pmc/Kconfig +14 -0
index e9012b703918..561d46634ab2 100644
@@ -9,6 +9,7 @@ config INTEL_PMC_CORE
 	depends on ACPI
 	depends on INTEL_PMT_TELEMETRY
 	select INTEL_PMC_SSRAM_TELEMETRY
+	select INTEL_PMC_PWRM_TELEMETRY
 	help
 	  The Intel Platform Controller Hub for Intel Core SoCs provides access
 	  to Power Management Controller registers via various interfaces. This
@@ -39,3 +40,16 @@ config INTEL_PMC_SSRAM_TELEMETRY
 	  (including sysfs).
 
 	  This option is selected by INTEL_PMC_CORE.
+
+config INTEL_PMC_PWRM_TELEMETRY
+	tristate
+	help
+	  This driver discovers PMC PWRM telemetry regions described in ACPI
+	  _DSD and registers them with the Intel VSEC framework as Intel PMT
+	  telemetry devices.
+
+	  It validates the ACPI discovery data and publishes the discovered
+	  regions so they can be accessed through the Intel PMT telemetry
+	  interfaces (including sysfs).
+
+	  This option is selected by INTEL_PMC_CORE.
drivers/platform/x86/intel/pmc/Makefile +2 -0
index 23853e867c91..5b595176f812 100644
@@ -13,3 +13,5 @@ obj-$(CONFIG_INTEL_PMC_CORE)		+= intel_pmc_core_pltdrv.o
 # Intel PMC SSRAM driver
 intel_pmc_ssram_telemetry-y		+= ssram_telemetry.o
 obj-$(CONFIG_INTEL_PMC_SSRAM_TELEMETRY)	+= intel_pmc_ssram_telemetry.o
+intel_pmc_pwrm_telemetry-y		+= pwrm_telemetry.o
+obj-$(CONFIG_INTEL_PMC_PWRM_TELEMETRY)	+= intel_pmc_pwrm_telemetry.o
drivers/platform/x86/intel/pmc/core.h +16 -0
index 55cf567febe4..b4c7399f8369 100644
@@ -14,10 +14,15 @@
 
 #include <linux/acpi.h>
 #include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/intel_vsec.h>
 #include <linux/platform_device.h>
+#include <linux/uuid.h>
 
 struct telem_endpoint;
 
+DEFINE_FREE(pmc_acpi_free, void *, if (_T) ACPI_FREE(_T))
+
 #define SLP_S0_RES_COUNTER_MASK			GENMASK(31, 0)
 
 #define PMC_BASE_ADDR_DEFAULT			0xFE000000
@@ -622,6 +627,8 @@ int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc,
 extern const struct file_operations pmc_core_substate_req_regs_fops;
 extern const struct file_operations pmc_core_substate_blk_req_fops;
 
+extern const guid_t intel_vsec_guid;
+
 #define pmc_for_each_mode(mode, pmc)						\
 	for (unsigned int __i = 0, __cond;					\
 	     __cond = __i < (pmc)->num_lpm_modes,				\
@@ -643,4 +650,13 @@ static const struct file_operations __name ## _fops = {			\
 	.release	= single_release,				\
 }
 
+struct intel_vsec_header;
+union acpi_object;
+
+/* Avoid checkpatch warning */
+typedef u32 (*acpi_disc_t)[PMT_DISC_DWORDS];
+
+acpi_disc_t pmc_parse_telem_dsd(union acpi_object *obj,
+				struct intel_vsec_header *header);
+union acpi_object *pmc_find_telem_guid(union acpi_object *dsd);
 #endif /* PMC_CORE_H */
drivers/platform/x86/intel/pmc/pwrm_telemetry.c new file +216 -0

216 lines not on this page · raw message

Signature · 1 line
2.43.0
author maintainer
#9 lore raw

PATCH v7 9/15 platform/x86/intel/pmc/ssram: Rename probe and PCI ID table for consistency

In reply to David E. Box

Rename intel_pmc_ssram_telemetry_probe() to pmc_ssram_telemetry_probe() and intel_pmc_ssram_telemetry_pci_ids[] to pmc_ssram_telemetry_pci_ids[], updating the MODULE_DEVICE_TABLE() and pci_driver wiring accordingly.

This aligns the symbol names with the driver filename and module name, reduces redundant intel_ prefixes, and improves readability. No functional behavior changes are intended.

---
V7 - No changes

V6 - No changes

V5 - No changes

V4 - No changes

V3 - No changes

V2 - No changes

 drivers/platform/x86/intel/pmc/ssram_telemetry.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
drivers/platform/x86/intel/pmc/ssram_telemetry.c +7 -7
index 6f6e83e70fc5..1deb4d71da3f 100644
@@ -149,7 +149,7 @@ int pmc_ssram_telemetry_get_pmc_info(unsigned int pmc_idx,
 }
 EXPORT_SYMBOL_GPL(pmc_ssram_telemetry_get_pmc_info);
 
-static int intel_pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_device_id *id)
+static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_device_id *id)
 {
 	int ret;
 
@@ -183,7 +183,7 @@ static int intel_pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct
 	return ret;
 }
 
-static const struct pci_device_id intel_pmc_ssram_telemetry_pci_ids[] = {
+static const struct pci_device_id pmc_ssram_telemetry_pci_ids[] = {
 	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_MTL_SOCM) },
 	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCS) },
 	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCM) },
@@ -193,14 +193,14 @@ static const struct pci_device_id intel_pmc_ssram_telemetry_pci_ids[] = {
 	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_WCL_PCDN) },
 	{ }
 };
-MODULE_DEVICE_TABLE(pci, intel_pmc_ssram_telemetry_pci_ids);
+MODULE_DEVICE_TABLE(pci, pmc_ssram_telemetry_pci_ids);
 
-static struct pci_driver intel_pmc_ssram_telemetry_driver = {
+static struct pci_driver pmc_ssram_telemetry_driver = {
 	.name = "intel_pmc_ssram_telemetry",
-	.id_table = intel_pmc_ssram_telemetry_pci_ids,
-	.probe = intel_pmc_ssram_telemetry_probe,
+	.id_table = pmc_ssram_telemetry_pci_ids,
+	.probe = pmc_ssram_telemetry_probe,
 };
-module_pci_driver(intel_pmc_ssram_telemetry_driver);
+module_pci_driver(pmc_ssram_telemetry_driver);
 
 MODULE_IMPORT_NS("INTEL_VSEC");
 MODULE_AUTHOR("Xi Pardee <[email protected]>");
Signature · 1 line
2.43.0
author maintainer
#10 lore raw

PATCH v7 10/15 platform/x86/intel/pmc/ssram: Add PCI platform data

In reply to David E. Box

Add per-device platform data for SSRAM telemetry PCI IDs and route probe through a method selector driven by id->driver_data.

This is a preparatory refactor for follow-on discovery methods while preserving current behavior: all supported IDs continue to use the PCI initialization path.

---
V7 changes:
- Changed pmc_ssram_telemetry_pci_init() to return 0 in the tail success path after secondary PMC probing (Ilpo).

V6 changes:
- Reordered from v5 patch 12 to maintain logical flow before probe state refactoring.

V5 - No changes

V4 - No changes

V3 - No changes

V2 changes:
- Added missing <linux/device.h> include for dev_dbg() usage in probe

 .../platform/x86/intel/pmc/ssram_telemetry.c  | 71 +++++++++++++++----
 1 file changed, 57 insertions(+), 14 deletions(-)
drivers/platform/x86/intel/pmc/ssram_telemetry.c +57 -14
index 1deb4d71da3f..05f213aa108f 100644
@@ -6,6 +6,7 @@
  */
 
 #include <linux/cleanup.h>
+#include <linux/device.h>
 #include <linux/intel_vsec.h>
 #include <linux/pci.h>
 #include <linux/types.h>
@@ -24,6 +25,18 @@
 
 DEFINE_FREE(pmc_ssram_telemetry_iounmap, void __iomem *, if (_T) iounmap(_T))
 
+enum resource_method {
+	RES_METHOD_PCI,
+};
+
+struct ssram_type {
+	enum resource_method method;
+};
+
+static const struct ssram_type pci_main = {
+	.method = RES_METHOD_PCI,
+};
+
 static struct pmc_ssram_telemetry *pmc_ssram_telems;
 static bool device_probed;
 
@@ -69,7 +82,7 @@ static inline u64 get_base(void __iomem *addr, u32 offset)
 }
 
 static int
-pmc_ssram_telemetry_get_pmc(struct pci_dev *pcidev, unsigned int pmc_idx, u32 offset)
+pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev, unsigned int pmc_idx, u32 offset)
 {
 	void __iomem __free(pmc_ssram_telemetry_iounmap) *tmp_ssram = NULL;
 	void __iomem __free(pmc_ssram_telemetry_iounmap) *ssram = NULL;
@@ -109,6 +122,20 @@ pmc_ssram_telemetry_get_pmc(struct pci_dev *pcidev, unsigned int pmc_idx, u32 of
 	return pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram);
 }
 
+static int pmc_ssram_telemetry_pci_init(struct pci_dev *pcidev)
+{
+	int ret;
+
+	ret = pmc_ssram_telemetry_get_pmc_pci(pcidev, PMC_IDX_MAIN, 0);
+	if (ret)
+		return ret;
+
+	pmc_ssram_telemetry_get_pmc_pci(pcidev, PMC_IDX_IOE, SSRAM_IOE_OFFSET);
+	pmc_ssram_telemetry_get_pmc_pci(pcidev, PMC_IDX_PCH, SSRAM_PCH_OFFSET);
+
+	return 0;
+}
+
 /**
  * pmc_ssram_telemetry_get_pmc_info() - Get a PMC devid and base_addr information
  * @pmc_idx:               Index of the PMC
@@ -151,6 +178,8 @@ EXPORT_SYMBOL_GPL(pmc_ssram_telemetry_get_pmc_info);
 
 static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_device_id *id)
 {
+	const struct ssram_type *ssram_type;
+	enum resource_method method;
 	int ret;
 
 	pmc_ssram_telems = devm_kzalloc(&pcidev->dev, sizeof(*pmc_ssram_telems) * MAX_NUM_PMC,
@@ -160,18 +189,25 @@ static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_de
 		goto probe_finish;
 	}
 
+	ssram_type = (const struct ssram_type *)id->driver_data;
+	if (!ssram_type) {
+		dev_dbg(&pcidev->dev, "missing driver data\n");
+		ret = -EINVAL;
+		goto probe_finish;
+	}
+
+	method = ssram_type->method;
+
 	ret = pcim_enable_device(pcidev);
 	if (ret) {
 		dev_dbg(&pcidev->dev, "failed to enable PMC SSRAM device\n");
 		goto probe_finish;
 	}
 
-	ret = pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_MAIN, 0);
-	if (ret)
-		goto probe_finish;
-
-	pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_IOE, SSRAM_IOE_OFFSET);
-	pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_PCH, SSRAM_PCH_OFFSET);
+	if (method == RES_METHOD_PCI)
+		ret = pmc_ssram_telemetry_pci_init(pcidev);
+	else
+		ret = -EINVAL;
 
 probe_finish:
 	/*
@@ -184,13 +220,20 @@ static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_de
 }
 
 static const struct pci_device_id pmc_ssram_telemetry_pci_ids[] = {
-	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_MTL_SOCM) },
-	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCS) },
-	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCM) },
-	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_LNL_SOCM) },
-	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDH) },
-	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDP) },
-	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_WCL_PCDN) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_MTL_SOCM),
+		.driver_data = (kernel_ulong_t)&pci_main },
+	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCS),
+		.driver_data = (kernel_ulong_t)&pci_main },
+	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCM),
+		.driver_data = (kernel_ulong_t)&pci_main },
+	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_LNL_SOCM),
+		.driver_data = (kernel_ulong_t)&pci_main },
+	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDH),
+		.driver_data = (kernel_ulong_t)&pci_main },
+	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDP),
+		.driver_data = (kernel_ulong_t)&pci_main },
+	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_WCL_PCDN),
+		.driver_data = (kernel_ulong_t)&pci_main },
 	{ }
 };
 MODULE_DEVICE_TABLE(pci, pmc_ssram_telemetry_pci_ids);
Signature · 1 line
2.43.0
author maintainer
#11 lore raw

PATCH v7 11/15 platform/x86/intel/pmc/ssram: Refactor DEVID/PWRMBASE extraction into helper

In reply to David E. Box

Move DEVID/PWRMBASE extraction into pmc_ssram_get_devid_pwrmbase().

This is a preparatory refactor to place functionality in a common helper for reuse by a subsequent patch. Additionally add missing bits.h include and define SSRAM_BASE_ADDR_MASK for the address extraction mask.

---
V7 - No changes

V6 - No changes

V5 - No changes

V4 - No changes

V3 - No changes

V2 changes:
- Added missing <linux/bits.h> include for GENMASK_ULL() used in get_base()
- Defined SSRAM_BASE_ADDR_MASK macro to replace magic mask constant

    GENMASK_ULL(63, 3)
 .../platform/x86/intel/pmc/ssram_telemetry.c  | 33 ++++++++++++-------
 1 file changed, 21 insertions(+), 12 deletions(-)
drivers/platform/x86/intel/pmc/ssram_telemetry.c +21 -12
index 05f213aa108f..211d842df449 100644
@@ -5,6 +5,7 @@
  * Copyright (c) 2023, Intel Corporation.
  */
 
+#include <linux/bits.h>
 #include <linux/cleanup.h>
 #include <linux/device.h>
 #include <linux/intel_vsec.h>
@@ -22,6 +23,7 @@
 #define SSRAM_PCH_OFFSET	0x60
 #define SSRAM_IOE_OFFSET	0x68
 #define SSRAM_DEVID_OFFSET	0x70
+#define SSRAM_BASE_ADDR_MASK	GENMASK_ULL(63, 3)
 
 DEFINE_FREE(pmc_ssram_telemetry_iounmap, void __iomem *, if (_T) iounmap(_T))
 
@@ -40,6 +42,23 @@ static const struct ssram_type pci_main = {
 static struct pmc_ssram_telemetry *pmc_ssram_telems;
 static bool device_probed;
 
+static inline u64 get_base(void __iomem *addr, u32 offset)
+{
+	return lo_hi_readq(addr + offset) & SSRAM_BASE_ADDR_MASK;
+}
+
+static void pmc_ssram_get_devid_pwrmbase(void __iomem *ssram, unsigned int pmc_idx)
+{
+	u64 pwrm_base;
+	u16 devid;
+
+	pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET);
+	devid = readw(ssram + SSRAM_DEVID_OFFSET);
+
+	pmc_ssram_telems[pmc_idx].devid = devid;
+	pmc_ssram_telems[pmc_idx].base_addr = pwrm_base;
+}
+
 static int
 pmc_ssram_telemetry_add_pmt(struct pci_dev *pcidev, u64 ssram_base, void __iomem *ssram)
 {
@@ -76,18 +95,12 @@ pmc_ssram_telemetry_add_pmt(struct pci_dev *pcidev, u64 ssram_base, void __iomem
 	return intel_vsec_register(&pcidev->dev, &info);
 }
 
-static inline u64 get_base(void __iomem *addr, u32 offset)
-{
-	return lo_hi_readq(addr + offset) & GENMASK_ULL(63, 3);
-}
-
 static int
 pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev, unsigned int pmc_idx, u32 offset)
 {
 	void __iomem __free(pmc_ssram_telemetry_iounmap) *tmp_ssram = NULL;
 	void __iomem __free(pmc_ssram_telemetry_iounmap) *ssram = NULL;
-	u64 ssram_base, pwrm_base;
-	u16 devid;
+	u64 ssram_base;
 
 	ssram_base = pci_resource_start(pcidev, 0);
 	tmp_ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
@@ -112,11 +125,7 @@ pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev, unsigned int pmc_idx, u3
 		ssram = no_free_ptr(tmp_ssram);
 	}
 
-	pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET);
-	devid = readw(ssram + SSRAM_DEVID_OFFSET);
-
-	pmc_ssram_telems[pmc_idx].devid = devid;
-	pmc_ssram_telems[pmc_idx].base_addr = pwrm_base;
+	pmc_ssram_get_devid_pwrmbase(ssram, pmc_idx);
 
 	/* Find and register and PMC telemetry entries */
 	return pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram);
Signature · 1 line
2.43.0
author maintainer
#12 lore raw

PATCH v7 12/15 platform/x86/intel/pmc/ssram: Switch to static array with per-index probe state

In reply to David E. Box

From: Xi Pardee <[email protected]>

Replace devm-allocated pmc_ssram_telems pointer with a fixed-size static array and introduce per-index probe state tracking.

This prepares the driver for later per-device probe handling where tying the PMC tracking storage to one probed PCI device is no longer suitable.

The previous single global device_probed flag cannot describe the state of individual PMC indices when multiple devices can be probed independently. Replace it with per-index state (UNPROBED, PROBING, PRESENT, ABSENT) and a staging cache that publishes discovered values only after probe completes. This avoids races between probe/unbind and concurrent readers.

Use marked state accesses with release/acquire ordering to prevent compiler and CPU reordering issues across concurrent probe/unbind cycles.

Assisted-by Claude:claude-sonnet-4-5

---
V7 changes:
- Made PMC_SSRAM_UNPROBED explicitly = 0 for clarity.
- Replace the explicit smp_wmb() / smp_rmb() pairing with smp_store_release() / smp_load_acquire() when publishing and consuming the per-index SSRAM state.

V6 changes:
- Squashed patch combining v5 patches 10 ("Use fixed-size static pmc array") and 13 ("Refactor memory barrier for reentrant probe"). Both patches addressed per-index probe state tracking and reentrant probe protection, so they were combined for better logical cohesion.
- Added per-index probe state enum (UNPROBED, PROBING, PRESENT, ABSENT) to replace devid overload where devid was used as both payload and probe state indicator. This fixes stale data issues on reprobe, distinguishes between -EAGAIN (probe in progress) and -ENODEV (probe failed) error semantics, and prevents stale values from being visible after failed reprobe (Ilpo/Sashiko/Claude).
- Added staging cache that publishes devid and base_addr only after probe completes successfully to avoid races between probe/unbind and concurrent readers.
- Added .remove callback to handle proper state cleanup on driver unbind.
- Used READ_ONCE/WRITE_ONCE for pmc_ssram_state[] accesses to prevent compiler optimizations from causing issues across concurrent probe/ unbind cycles.

V5 - No changes (for both original patches)

V4 - No changes (for both original patches)

V3 - No changes (for both original patches)

V2 changes (from original patch 10 "Use fixed-size static pmc array"):
- Replaced hardcoded array size [3] with MAX_NUM_PMC constant

V2 changes (from original patch 13 "Refactor memory barrier"):
- Expanded commit message to explain synchronization rationale
- Remove unused probe_finish label associated with the old global flag

 .../platform/x86/intel/pmc/ssram_telemetry.c  | 202 ++++++++++++++----
 1 file changed, 160 insertions(+), 42 deletions(-)
drivers/platform/x86/intel/pmc/ssram_telemetry.c +160 -42
index 211d842df449..110ea2dfa542 100644
@@ -5,6 +5,7 @@
  * Copyright (c) 2023, Intel Corporation.
  */
 
+#include <linux/bitmap.h>
 #include <linux/bits.h>
 #include <linux/cleanup.h>
 #include <linux/device.h>
@@ -24,6 +25,7 @@
 #define SSRAM_IOE_OFFSET	0x68
 #define SSRAM_DEVID_OFFSET	0x70
 #define SSRAM_BASE_ADDR_MASK	GENMASK_ULL(63, 3)
+#define SSRAM_PCI_PMC_MASK	(BIT(PMC_IDX_MAIN) | BIT(PMC_IDX_IOE) | BIT(PMC_IDX_PCH))
 
 DEFINE_FREE(pmc_ssram_telemetry_iounmap, void __iomem *, if (_T) iounmap(_T))
 
@@ -39,15 +41,33 @@ static const struct ssram_type pci_main = {
 	.method = RES_METHOD_PCI,
 };
 
-static struct pmc_ssram_telemetry *pmc_ssram_telems;
-static bool device_probed;
+enum pmc_ssram_state {
+	PMC_SSRAM_UNPROBED = 0,
+	PMC_SSRAM_PROBING,
+	PMC_SSRAM_PRESENT,
+	PMC_SSRAM_ABSENT,
+};
+
+static enum pmc_ssram_state pmc_ssram_state[MAX_NUM_PMC];
+static struct pmc_ssram_telemetry pmc_ssram_telems[MAX_NUM_PMC];
+
+struct pmc_ssram_probe_cache {
+	struct pmc_ssram_telemetry telems[MAX_NUM_PMC];
+	unsigned long owned_mask;
+	unsigned long valid_mask;
+};
+
+struct pmc_ssram_drvdata {
+	unsigned long owned_mask;
+};
 
 static inline u64 get_base(void __iomem *addr, u32 offset)
 {
 	return lo_hi_readq(addr + offset) & SSRAM_BASE_ADDR_MASK;
 }
 
-static void pmc_ssram_get_devid_pwrmbase(void __iomem *ssram, unsigned int pmc_idx)
+static void pmc_ssram_get_devid_pwrmbase(struct pmc_ssram_probe_cache *probe_cache,
+					 void __iomem *ssram, unsigned int pmc_idx)
 {
 	u64 pwrm_base;
 	u16 devid;
@@ -55,8 +75,45 @@ static void pmc_ssram_get_devid_pwrmbase(void __iomem *ssram, unsigned int pmc_i
 	pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET);
 	devid = readw(ssram + SSRAM_DEVID_OFFSET);
 
-	pmc_ssram_telems[pmc_idx].devid = devid;
-	pmc_ssram_telems[pmc_idx].base_addr = pwrm_base;
+	probe_cache->telems[pmc_idx].base_addr = pwrm_base;
+	probe_cache->telems[pmc_idx].devid = devid;
+}
+
+static void pmc_ssram_publish_absent(unsigned int pmc_idx)
+{
+	/*
+	 * Publish only the state without modifying base_addr and devid. This
+	 * lets a reader that already observed PRESENT finish copying the
+	 * previous values even if unbind concurrently publishes ABSENT. Readers
+	 * that observe ABSENT return -ENODEV without accessing data.
+	 */
+	smp_store_release(&pmc_ssram_state[pmc_idx], PMC_SSRAM_ABSENT);
+}
+
+static void pmc_ssram_publish_present(struct pmc_ssram_probe_cache *probe_cache,
+				      unsigned int pmc_idx)
+{
+	/*
+	 * The devid and base_addr fields are read from hardware MMIO registers
+	 * whose values are stable for a given PMC index. A reader that observed
+	 * PRESENT from an earlier probe can safely copy them while a concurrent
+	 * rebind republishes those fields because both probes read the same
+	 * hardware values.
+	 */
+	pmc_ssram_telems[pmc_idx] = probe_cache->telems[pmc_idx];
+	/*
+	 * Publish base_addr and devid before publishing PRESENT. Pairs with the
+	 * acquire load in the reader that consumes them after observing PRESENT.
+	 */
+	smp_store_release(&pmc_ssram_state[pmc_idx], PMC_SSRAM_PRESENT);
+}
+
+static void pmc_ssram_mark_probing(unsigned long mask)
+{
+	unsigned long bit;
+
+	for_each_set_bit(bit, &mask, MAX_NUM_PMC)
+		WRITE_ONCE(pmc_ssram_state[bit], PMC_SSRAM_PROBING);
 }
 
 static int
@@ -96,11 +153,14 @@ pmc_ssram_telemetry_add_pmt(struct pci_dev *pcidev, u64 ssram_base, void __iomem
 }
 
 static int
-pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev, unsigned int pmc_idx, u32 offset)
+pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev,
+				struct pmc_ssram_probe_cache *probe_cache,
+				unsigned int pmc_idx, u32 offset)
 {
 	void __iomem __free(pmc_ssram_telemetry_iounmap) *tmp_ssram = NULL;
 	void __iomem __free(pmc_ssram_telemetry_iounmap) *ssram = NULL;
 	u64 ssram_base;
+	int ret;
 
 	ssram_base = pci_resource_start(pcidev, 0);
 	tmp_ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
@@ -125,22 +185,38 @@ pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev, unsigned int pmc_idx, u3
 		ssram = no_free_ptr(tmp_ssram);
 	}
 
-	pmc_ssram_get_devid_pwrmbase(ssram, pmc_idx);
+	pmc_ssram_get_devid_pwrmbase(probe_cache, ssram, pmc_idx);
 
 	/* Find and register and PMC telemetry entries */
-	return pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram);
+	ret = pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram);
+	if (ret)
+		return ret;
+
+	probe_cache->valid_mask |= BIT(pmc_idx);
+
+	return 0;
 }
 
-static int pmc_ssram_telemetry_pci_init(struct pci_dev *pcidev)
+static int pmc_ssram_telemetry_pci_init(struct pci_dev *pcidev,
+					struct pmc_ssram_probe_cache *probe_cache)
 {
 	int ret;
 
-	ret = pmc_ssram_telemetry_get_pmc_pci(pcidev, PMC_IDX_MAIN, 0);
+	ret = pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_MAIN, 0);
 	if (ret)
 		return ret;
 
-	pmc_ssram_telemetry_get_pmc_pci(pcidev, PMC_IDX_IOE, SSRAM_IOE_OFFSET);
-	pmc_ssram_telemetry_get_pmc_pci(pcidev, PMC_IDX_PCH, SSRAM_PCH_OFFSET);
+	/*
+	 * If MAIN PMC enumeration is successful but either IOE or PCH fail,
+	 * don't fail probe as the MAIN PMC is still useful as it provides the
+	 * global reset and slp_s0 counter access. Failed or missing secondary
+	 * PMCs are left out of valid_mask and published as absent.
+	 */
+	pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_IOE,
+					SSRAM_IOE_OFFSET);
+
+	pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_PCH,
+					SSRAM_PCH_OFFSET);
 
 	return 0;
 }
@@ -154,58 +230,90 @@ static int pmc_ssram_telemetry_pci_init(struct pci_dev *pcidev)
  * * 0           - Success
  * * -EAGAIN     - Probe function has not finished yet. Try again.
  * * -EINVAL     - Invalid pmc_idx
- * * -ENODEV     - PMC device is not available
+ * * -ENODEV     - PMC device is not available (hardware absent or driver failed to initialize)
  */
 int pmc_ssram_telemetry_get_pmc_info(unsigned int pmc_idx,
 				     struct pmc_ssram_telemetry *pmc_ssram_telemetry)
 {
+	enum pmc_ssram_state state;
+
+	if (pmc_idx >= MAX_NUM_PMC)
+		return -EINVAL;
+
 	/*
 	 * PMCs are discovered in probe function. If this function is called before
-	 * probe function complete, the result would be invalid. Use device_probed
-	 * variable to avoid this case. Return -EAGAIN to inform the consumer to call
-	 * again later.
+	 * probe function complete, the result would be invalid. Use per-PMC state
+	 * to inform the consumer to call again later.
 	 */
-	if (!device_probed)
+	state = smp_load_acquire(&pmc_ssram_state[pmc_idx]);
+	if (state == PMC_SSRAM_UNPROBED || state == PMC_SSRAM_PROBING)
 		return -EAGAIN;
 
-	/*
-	 * Memory barrier is used to ensure the correct read order between
-	 * device_probed variable and PMC info.
-	 */
-	smp_rmb();
-	if (pmc_idx >= MAX_NUM_PMC)
-		return -EINVAL;
-
-	if (!pmc_ssram_telems || !pmc_ssram_telems[pmc_idx].devid)
+	if (state == PMC_SSRAM_ABSENT)
 		return -ENODEV;
 
+	/*
+	 * Acquire semantics order reads of devid and base_addr after observing
+	 * PRESENT and pair with the writer's release-store.
+	 */
 	pmc_ssram_telemetry->devid = pmc_ssram_telems[pmc_idx].devid;
 	pmc_ssram_telemetry->base_addr = pmc_ssram_telems[pmc_idx].base_addr;
 	return 0;
 }
 EXPORT_SYMBOL_GPL(pmc_ssram_telemetry_get_pmc_info);
 
+static void pmc_ssram_publish_absent_mask(unsigned long mask)
+{
+	unsigned long bit;
+
+	for_each_set_bit(bit, &mask, MAX_NUM_PMC)
+		pmc_ssram_publish_absent(bit);
+}
+
+static void pmc_ssram_publish_telems(struct pmc_ssram_probe_cache *probe_cache, int ret)
+{
+	unsigned long bit;
+
+	if (ret) {
+		pmc_ssram_publish_absent_mask(probe_cache->owned_mask);
+		return;
+	}
+
+	for_each_set_bit(bit, &probe_cache->owned_mask, MAX_NUM_PMC) {
+		if (probe_cache->valid_mask & BIT(bit))
+			pmc_ssram_publish_present(probe_cache, bit);
+		else
+			pmc_ssram_publish_absent(bit);
+	}
+}
+
 static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_device_id *id)
 {
+	struct pmc_ssram_probe_cache probe_cache = {};
+	struct pmc_ssram_drvdata *drvdata;
 	const struct ssram_type *ssram_type;
 	enum resource_method method;
 	int ret;
 
-	pmc_ssram_telems = devm_kzalloc(&pcidev->dev, sizeof(*pmc_ssram_telems) * MAX_NUM_PMC,
-					GFP_KERNEL);
-	if (!pmc_ssram_telems) {
-		ret = -ENOMEM;
-		goto probe_finish;
-	}
-
 	ssram_type = (const struct ssram_type *)id->driver_data;
 	if (!ssram_type) {
 		dev_dbg(&pcidev->dev, "missing driver data\n");
-		ret = -EINVAL;
-		goto probe_finish;
+		return -EINVAL;
 	}
 
 	method = ssram_type->method;
+	if (method == RES_METHOD_PCI)
+		probe_cache.owned_mask = SSRAM_PCI_PMC_MASK;
+	else
+		return -EINVAL;
+
+	pmc_ssram_mark_probing(probe_cache.owned_mask);
+
+	drvdata = devm_kzalloc(&pcidev->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata) {
+		ret = -ENOMEM;
+		goto probe_finish;
+	}
 
 	ret = pcim_enable_device(pcidev);
 	if (ret) {
@@ -214,20 +322,29 @@ static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_de
 	}
 
 	if (method == RES_METHOD_PCI)
-		ret = pmc_ssram_telemetry_pci_init(pcidev);
+		ret = pmc_ssram_telemetry_pci_init(pcidev, &probe_cache);
 	else
 		ret = -EINVAL;
 
+	if (!ret) {
+		drvdata->owned_mask = probe_cache.owned_mask;
+		pci_set_drvdata(pcidev, drvdata);
+	}
+
 probe_finish:
-	/*
-	 * Memory barrier is used to ensure the correct write order between PMC info
-	 * and device_probed variable.
-	 */
-	smp_wmb();
-	device_probed = true;
+	pmc_ssram_publish_telems(&probe_cache, ret);
+
 	return ret;
 }
 
+static void pmc_ssram_telemetry_remove(struct pci_dev *pcidev)
+{
+	struct pmc_ssram_drvdata *drvdata = pci_get_drvdata(pcidev);
+
+	if (drvdata)
+		pmc_ssram_publish_absent_mask(drvdata->owned_mask);
+}
+
 static const struct pci_device_id pmc_ssram_telemetry_pci_ids[] = {
 	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_MTL_SOCM),
 		.driver_data = (kernel_ulong_t)&pci_main },
@@ -251,6 +368,7 @@ static struct pci_driver pmc_ssram_telemetry_driver = {
 	.name = "intel_pmc_ssram_telemetry",
 	.id_table = pmc_ssram_telemetry_pci_ids,
 	.probe = pmc_ssram_telemetry_probe,
+	.remove = pmc_ssram_telemetry_remove,
 };
 module_pci_driver(pmc_ssram_telemetry_driver);
 
Signature · 1 line
2.43.0
author maintainer
#15 lore raw

PATCH v7 13/15 platform/x86/intel/pmc/ssram: Add ACPI discovery scaffolding

In reply to David E. Box

Prepare the SSRAM telemetry driver for ACPI-based discovery by adding support for reading telemetry regions from ACPI _DSD properties.

Add pmc_ssram_telemetry_acpi_init() to parse _DSD for telemetry discovery tables and register them with the Intel VSEC framework. Extend ssram_type with a p_index field to specify which PMC index each ACPI device owns (unlike PCI which discovers all three PMCs from one device).

At this stage, no platform IDs are wired to use ACPI discovery - existing devices continue using the PCI path. Follow-on patches will add platform support.

Assisted-by Claude:claude-sonnet-4-5

---
V7 - No changes

V6 - No changes

V5 changes:
- Fix dsd_buf leak by moving the __free(pmc_acpi_free) declaration after acpi_evaluate_object() populates buf.pointer, and switched pmc_find_telem_guid(buf.pointer) to operate on dsd_buf so cleanup releases the actual allocation.
- Split acpi_handle declaration from ACPI_HANDLE() assignment and placed the assignment immediately before the !handle check (Ilpo).
- Reordered local variables in pmc_ssram_telemetry_acpi_init() in reverse-xmas-tree order (Ilpo).

V4 - Replaced local raw ACPI discovery pointer type u32 (*)[4] with
acpi_disc_t in SSRAM ACPI initialization path.

V3 - No changes

V2 changes:
- Fixed cleanup patterns using __free() attributes
- Addressed Ilpo's recommendations for safer cleanup.h patterns

 .../platform/x86/intel/pmc/ssram_telemetry.c  | 78 +++++++++++++++++++
 1 file changed, 78 insertions(+)
drivers/platform/x86/intel/pmc/ssram_telemetry.c +78 -0
index 110ea2dfa542..35c6f0261567 100644
@@ -5,6 +5,7 @@
  * Copyright (c) 2023, Intel Corporation.
  */
 
+#include <linux/acpi.h>
 #include <linux/bitmap.h>
 #include <linux/bits.h>
 #include <linux/cleanup.h>
@@ -31,14 +32,17 @@ DEFINE_FREE(pmc_ssram_telemetry_iounmap, void __iomem *, if (_T) iounmap(_T))
 
 enum resource_method {
 	RES_METHOD_PCI,
+	RES_METHOD_ACPI,
 };
 
 struct ssram_type {
 	enum resource_method method;
+	enum pmc_index p_index;
 };
 
 static const struct ssram_type pci_main = {
 	.method = RES_METHOD_PCI,
+	.p_index = PMC_IDX_MAIN,
 };
 
 enum pmc_ssram_state {
@@ -221,6 +225,73 @@ static int pmc_ssram_telemetry_pci_init(struct pci_dev *pcidev,
 	return 0;
 }
 
+static int pmc_ssram_telemetry_get_pmc_acpi(struct pci_dev *pcidev,
+					    struct pmc_ssram_probe_cache *probe_cache,
+					    unsigned int pmc_idx)
+{
+	u64 ssram_base;
+
+	ssram_base = pci_resource_start(pcidev, 0);
+	if (!ssram_base)
+		return -ENODEV;
+
+	void __iomem __free(pmc_ssram_telemetry_iounmap) *ssram =
+		ioremap(ssram_base, SSRAM_HDR_SIZE);
+	if (!ssram)
+		return -ENOMEM;
+
+	pmc_ssram_get_devid_pwrmbase(probe_cache, ssram, pmc_idx);
+	probe_cache->valid_mask |= BIT(pmc_idx);
+
+	return 0;
+}
+
+static int pmc_ssram_telemetry_acpi_init(struct pci_dev *pcidev,
+					 struct pmc_ssram_probe_cache *probe_cache,
+					 enum pmc_index index)
+{
+	struct intel_vsec_header header;
+	struct intel_vsec_header *headers[2] = { &header, NULL };
+	struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct intel_vsec_platform_info info = { };
+	union acpi_object *dsd;
+	acpi_handle handle;
+	acpi_status status;
+	int ret;
+
+	handle = ACPI_HANDLE(&pcidev->dev);
+	if (!handle)
+		return -ENODEV;
+
+	status = acpi_evaluate_object(handle, "_DSD", NULL, &buf);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	void *dsd_buf __free(pmc_acpi_free) = buf.pointer;
+
+	dsd = pmc_find_telem_guid(dsd_buf);
+	if (!dsd)
+		return -ENODEV;
+
+	acpi_disc_t disc __free(kfree) = pmc_parse_telem_dsd(dsd, &header);
+	if (IS_ERR(disc))
+		return PTR_ERR(disc);
+
+	info.headers = headers;
+	info.caps = VSEC_CAP_TELEMETRY;
+	info.acpi_disc = disc;
+	info.src = INTEL_VSEC_DISC_ACPI;
+
+	/* This is an ACPI companion device. PCI BAR will be used for base addr. */
+	info.base_addr = 0;
+
+	ret = intel_vsec_register(&pcidev->dev, &info);
+	if (ret)
+		return ret;
+
+	return pmc_ssram_telemetry_get_pmc_acpi(pcidev, probe_cache, index);
+}
+
 /**
  * pmc_ssram_telemetry_get_pmc_info() - Get a PMC devid and base_addr information
  * @pmc_idx:               Index of the PMC
@@ -293,6 +364,7 @@ static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_de
 	struct pmc_ssram_drvdata *drvdata;
 	const struct ssram_type *ssram_type;
 	enum resource_method method;
+	enum pmc_index index;
 	int ret;
 
 	ssram_type = (const struct ssram_type *)id->driver_data;
@@ -301,9 +373,12 @@ static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_de
 		return -EINVAL;
 	}
 
+	index = ssram_type->p_index;
 	method = ssram_type->method;
 	if (method == RES_METHOD_PCI)
 		probe_cache.owned_mask = SSRAM_PCI_PMC_MASK;
+	else if (method == RES_METHOD_ACPI)
+		probe_cache.owned_mask = BIT(index);
 	else
 		return -EINVAL;
 
@@ -323,6 +398,8 @@ static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_de
 
 	if (method == RES_METHOD_PCI)
 		ret = pmc_ssram_telemetry_pci_init(pcidev, &probe_cache);
+	else if (method == RES_METHOD_ACPI)
+		ret = pmc_ssram_telemetry_acpi_init(pcidev, &probe_cache, index);
 	else
 		ret = -EINVAL;
 
@@ -373,6 +450,7 @@ static struct pci_driver pmc_ssram_telemetry_driver = {
 module_pci_driver(pmc_ssram_telemetry_driver);
 
 MODULE_IMPORT_NS("INTEL_VSEC");
+MODULE_IMPORT_NS("INTEL_PMC_CORE");
 MODULE_AUTHOR("Xi Pardee <[email protected]>");
 MODULE_DESCRIPTION("Intel PMC SSRAM Telemetry driver");
 MODULE_LICENSE("GPL");
Signature · 1 line
2.43.0
author maintainer
#16 lore raw

PATCH v7 14/15 platform/x86/intel/pmc/ssram: Make PMT registration optional

In reply to David E. Box

The SSRAM telemetry driver extracts essential PMC device ID and power management base address information that intel_pmc_core depends on for core functionality. If PMT registration failure prevents this critical data from being available, intel_pmc_core operation would break entirely. Therefore, PMT registration failures must not block access to this data.

Change the behavior to log a warning when PMT registration fails but continue with successful driver initialization, ensuring the primary telemetry data remains accessible to dependent drivers.

---
V7 - No changes

V6 - No changes

V5 - No changes

V4 - No changes

V3 changes:
- Dropped the standalone cleanup-pattern patch from this refreshed series retaining the simpler ssram pointer flow requested in review.
- Folded PMT-registration-optional handling onto that simpler flow with no intended functional change.

V2 changes:
- Update commit message for clarity
- Also apply the PCI telemetry path

 drivers/platform/x86/intel/pmc/ssram_telemetry.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/platform/x86/intel/pmc/ssram_telemetry.c +2 -2
index 35c6f0261567..2095df6a42b2 100644
@@ -194,7 +194,7 @@ pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev,
 	/* Find and register and PMC telemetry entries */
 	ret = pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram);
 	if (ret)
-		return ret;
+		dev_warn(&pcidev->dev, "could not register PMT\n");
 
 	probe_cache->valid_mask |= BIT(pmc_idx);
 
@@ -287,7 +287,7 @@ static int pmc_ssram_telemetry_acpi_init(struct pci_dev *pcidev,
 
 	ret = intel_vsec_register(&pcidev->dev, &info);
 	if (ret)
-		return ret;
+		dev_warn(&pcidev->dev, "could not register PMT\n");
 
 	return pmc_ssram_telemetry_get_pmc_acpi(pcidev, probe_cache, index);
 }
Signature · 1 line
2.43.0
author maintainer
#17 lore raw

PATCH v7 15/15 platform/x86/intel/pmc: Add NVL PCI IDs for SSRAM telemetry discovery

In reply to David E. Box

Add Nova Lake S PMC device IDs to enable binding of the SSRAM telemetry driver on NVL platforms, and map them to the ACPI-based discovery policy.

---
V7 - No changes

V6 - Dropped NVL product defines in core.h as they we already included in
a prior patch from Xi.

V5 - No changes

V4 - No changes

V3 - No changes

V2 - No changes

 drivers/platform/x86/intel/pmc/ssram_telemetry.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
drivers/platform/x86/intel/pmc/ssram_telemetry.c +16 -0
index 2095df6a42b2..2c9d47070f81 100644
@@ -45,6 +45,16 @@ static const struct ssram_type pci_main = {
 	.p_index = PMC_IDX_MAIN,
 };
 
+static const struct ssram_type acpi_main = {
+	.method = RES_METHOD_ACPI,
+	.p_index = PMC_IDX_MAIN,
+};
+
+static const struct ssram_type acpi_pch = {
+	.method = RES_METHOD_ACPI,
+	.p_index = PMC_IDX_PCH,
+};
+
 enum pmc_ssram_state {
 	PMC_SSRAM_UNPROBED = 0,
 	PMC_SSRAM_PROBING,
@@ -437,6 +447,12 @@ static const struct pci_device_id pmc_ssram_telemetry_pci_ids[] = {
 		.driver_data = (kernel_ulong_t)&pci_main },
 	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_WCL_PCDN),
 		.driver_data = (kernel_ulong_t)&pci_main },
+	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_NVL_PCDH),
+		.driver_data = (kernel_ulong_t)&acpi_main },
+	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_NVL_PCDS),
+		.driver_data = (kernel_ulong_t)&acpi_main },
+	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_NVL_PCHS),
+		.driver_data = (kernel_ulong_t)&acpi_pch },
 	{ }
 };
 MODULE_DEVICE_TABLE(pci, pmc_ssram_telemetry_pci_ids);
Signature · 1 line
2.43.0
maintainer
#13 lore raw

Re: [PATCH v7 12/15] platform/x86/intel/pmc/ssram: Switch to static array with per-index probe state

In reply to David E. Box
Quoted 14 lines · David E. Box

From: Xi Pardee <[email protected]>

Replace devm-allocated pmc_ssram_telems pointer with a fixed-size static array and introduce per-index probe state tracking.

This prepares the driver for later per-device probe handling where tying the PMC tracking storage to one probed PCI device is no longer suitable.

The previous single global device_probed flag cannot describe the state of individual PMC indices when multiple devices can be probed independently. Replace it with per-index state (UNPROBED, PROBING, PRESENT, ABSENT) and a staging cache that publishes discovered values only after probe completes. This avoids races between probe/unbind and concurrent readers.

Quoted 4 lines

Use marked state accesses with release/acquire ordering to prevent compiler and CPU reordering issues across concurrent probe/unbind cycles.

Hi,

I started to wonder is this anymore Xi's handiwork? This patch as been rewritten so totally from the earlier that I cannot really see much from the earlier remaining, so is Xi still the right author for it? (It might be correct, I just don't know who did the actual work here).

Quoted 74 lines
Assisted-by Claude:claude-sonnet-4-5

---
V7 changes:
- Made PMC_SSRAM_UNPROBED explicitly = 0 for clarity.
- Replace the explicit smp_wmb() / smp_rmb() pairing with smp_store_release() / smp_load_acquire() when publishing and consuming the per-index SSRAM state.

V6 changes:
- Squashed patch combining v5 patches 10 ("Use fixed-size static pmc array") and 13 ("Refactor memory barrier for reentrant probe"). Both patches addressed per-index probe state tracking and reentrant probe protection, so they were combined for better logical cohesion.
- Added per-index probe state enum (UNPROBED, PROBING, PRESENT, ABSENT) to replace devid overload where devid was used as both payload and probe state indicator. This fixes stale data issues on reprobe, distinguishes between -EAGAIN (probe in progress) and -ENODEV (probe failed) error semantics, and prevents stale values from being visible after failed reprobe (Ilpo/Sashiko/Claude).
- Added staging cache that publishes devid and base_addr only after probe completes successfully to avoid races between probe/unbind and concurrent readers.
- Added .remove callback to handle proper state cleanup on driver unbind.
- Used READ_ONCE/WRITE_ONCE for pmc_ssram_state[] accesses to prevent compiler optimizations from causing issues across concurrent probe/ unbind cycles.

V5 - No changes (for both original patches)

V4 - No changes (for both original patches)

V3 - No changes (for both original patches)

V2 changes (from original patch 10 "Use fixed-size static pmc array"):
- Replaced hardcoded array size [3] with MAX_NUM_PMC constant

V2 changes (from original patch 13 "Refactor memory barrier"):
- Expanded commit message to explain synchronization rationale
- Remove unused probe_finish label associated with the old global flag

 .../platform/x86/intel/pmc/ssram_telemetry.c  | 202 ++++++++++++++----
 1 file changed, 160 insertions(+), 42 deletions(-)
drivers/platform/x86/intel/pmc/ssram_telemetry.c +6 -2
index 211d842df449..110ea2dfa542 100644
@@ -5,6 +5,7 @@
  * Copyright (c) 2023, Intel Corporation.
  */
 
+#include <linux/bitmap.h>
 #include <linux/bits.h>
 #include <linux/cleanup.h>
 #include <linux/device.h>
@@ -24,6 +25,7 @@
 #define SSRAM_IOE_OFFSET	0x68
 #define SSRAM_DEVID_OFFSET	0x70
 #define SSRAM_BASE_ADDR_MASK	GENMASK_ULL(63, 3)
+#define SSRAM_PCI_PMC_MASK	(BIT(PMC_IDX_MAIN) | BIT(PMC_IDX_IOE) | BIT(PMC_IDX_PCH))
 
 DEFINE_FREE(pmc_ssram_telemetry_iounmap, void __iomem *, if (_T) iounmap(_T))
 
@@ -39,15 +41,33 @@ static const struct ssram_type pci_main = {
 	.method = RES_METHOD_PCI,
 };
 
-static struct pmc_ssram_telemetry *pmc_ssram_telems;
-static bool device_probed;
+enum pmc_ssram_state {
+	PMC_SSRAM_UNPROBED = 0,
+	PMC_SSRAM_PROBING,
+	PMC_SSRAM_PRESENT,

drivers/platform/x86/intel/pmc/ssram_telemetry.c static const struct ssram_type pci_main = { · line 57

Quoted 10 lines
drivers/platform/x86/intel/pmc/ssram_telemetry.c +10 -0
+	PMC_SSRAM_ABSENT,
+};
+
+static enum pmc_ssram_state pmc_ssram_state[MAX_NUM_PMC];
+static struct pmc_ssram_telemetry pmc_ssram_telems[MAX_NUM_PMC];
+
+struct pmc_ssram_probe_cache {
+	struct pmc_ssram_telemetry telems[MAX_NUM_PMC];
+	unsigned long owned_mask;
+	unsigned long valid_mask;

I went through this change again and as far as I could understand it, it looks okay to me.

But, could you please suggest some descriptive comments to these (or kerneldoc for the entire struct). I think it would be helpful for understanding the state tracking algorithm to have the intent for each of these stated upfront (rather than code reader having to decipher it from the code like I had to do).

I can try to put the comment(s) in while applying (unless you want to send an updated version of the entire series yourself which would be fine as well and save me little bit of editing effort).

Signature · 1 line
 i.
Quoted 286 lines
drivers/platform/x86/intel/pmc/ssram_telemetry.c +144 -40
+};
+
+struct pmc_ssram_drvdata {
+	unsigned long owned_mask;
+};
 
 static inline u64 get_base(void __iomem *addr, u32 offset)
 {
 	return lo_hi_readq(addr + offset) & SSRAM_BASE_ADDR_MASK;
 }
 
-static void pmc_ssram_get_devid_pwrmbase(void __iomem *ssram, unsigned int pmc_idx)
+static void pmc_ssram_get_devid_pwrmbase(struct pmc_ssram_probe_cache *probe_cache,
+					 void __iomem *ssram, unsigned int pmc_idx)
 {
 	u64 pwrm_base;
 	u16 devid;
@@ -55,8 +75,45 @@ static void pmc_ssram_get_devid_pwrmbase(void __iomem *ssram, unsigned int pmc_i
 	pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET);
 	devid = readw(ssram + SSRAM_DEVID_OFFSET);
 
-	pmc_ssram_telems[pmc_idx].devid = devid;
-	pmc_ssram_telems[pmc_idx].base_addr = pwrm_base;
+	probe_cache->telems[pmc_idx].base_addr = pwrm_base;
+	probe_cache->telems[pmc_idx].devid = devid;
+}
+
+static void pmc_ssram_publish_absent(unsigned int pmc_idx)
+{
+	/*
+	 * Publish only the state without modifying base_addr and devid. This
+	 * lets a reader that already observed PRESENT finish copying the
+	 * previous values even if unbind concurrently publishes ABSENT. Readers
+	 * that observe ABSENT return -ENODEV without accessing data.
+	 */
+	smp_store_release(&pmc_ssram_state[pmc_idx], PMC_SSRAM_ABSENT);
+}
+
+static void pmc_ssram_publish_present(struct pmc_ssram_probe_cache *probe_cache,
+				      unsigned int pmc_idx)
+{
+	/*
+	 * The devid and base_addr fields are read from hardware MMIO registers
+	 * whose values are stable for a given PMC index. A reader that observed
+	 * PRESENT from an earlier probe can safely copy them while a concurrent
+	 * rebind republishes those fields because both probes read the same
+	 * hardware values.
+	 */
+	pmc_ssram_telems[pmc_idx] = probe_cache->telems[pmc_idx];
+	/*
+	 * Publish base_addr and devid before publishing PRESENT. Pairs with the
+	 * acquire load in the reader that consumes them after observing PRESENT.
+	 */
+	smp_store_release(&pmc_ssram_state[pmc_idx], PMC_SSRAM_PRESENT);
+}
+
+static void pmc_ssram_mark_probing(unsigned long mask)
+{
+	unsigned long bit;
+
+	for_each_set_bit(bit, &mask, MAX_NUM_PMC)
+		WRITE_ONCE(pmc_ssram_state[bit], PMC_SSRAM_PROBING);
 }
 
 static int
@@ -96,11 +153,14 @@ pmc_ssram_telemetry_add_pmt(struct pci_dev *pcidev, u64 ssram_base, void __iomem
 }
 
 static int
-pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev, unsigned int pmc_idx, u32 offset)
+pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev,
+				struct pmc_ssram_probe_cache *probe_cache,
+				unsigned int pmc_idx, u32 offset)
 {
 	void __iomem __free(pmc_ssram_telemetry_iounmap) *tmp_ssram = NULL;
 	void __iomem __free(pmc_ssram_telemetry_iounmap) *ssram = NULL;
 	u64 ssram_base;
+	int ret;
 
 	ssram_base = pci_resource_start(pcidev, 0);
 	tmp_ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
@@ -125,22 +185,38 @@ pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev, unsigned int pmc_idx, u3
 		ssram = no_free_ptr(tmp_ssram);
 	}
 
-	pmc_ssram_get_devid_pwrmbase(ssram, pmc_idx);
+	pmc_ssram_get_devid_pwrmbase(probe_cache, ssram, pmc_idx);
 
 	/* Find and register and PMC telemetry entries */
-	return pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram);
+	ret = pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram);
+	if (ret)
+		return ret;
+
+	probe_cache->valid_mask |= BIT(pmc_idx);
+
+	return 0;
 }
 
-static int pmc_ssram_telemetry_pci_init(struct pci_dev *pcidev)
+static int pmc_ssram_telemetry_pci_init(struct pci_dev *pcidev,
+					struct pmc_ssram_probe_cache *probe_cache)
 {
 	int ret;
 
-	ret = pmc_ssram_telemetry_get_pmc_pci(pcidev, PMC_IDX_MAIN, 0);
+	ret = pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_MAIN, 0);
 	if (ret)
 		return ret;
 
-	pmc_ssram_telemetry_get_pmc_pci(pcidev, PMC_IDX_IOE, SSRAM_IOE_OFFSET);
-	pmc_ssram_telemetry_get_pmc_pci(pcidev, PMC_IDX_PCH, SSRAM_PCH_OFFSET);
+	/*
+	 * If MAIN PMC enumeration is successful but either IOE or PCH fail,
+	 * don't fail probe as the MAIN PMC is still useful as it provides the
+	 * global reset and slp_s0 counter access. Failed or missing secondary
+	 * PMCs are left out of valid_mask and published as absent.
+	 */
+	pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_IOE,
+					SSRAM_IOE_OFFSET);
+
+	pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_PCH,
+					SSRAM_PCH_OFFSET);
 
 	return 0;
 }
@@ -154,58 +230,90 @@ static int pmc_ssram_telemetry_pci_init(struct pci_dev *pcidev)
  * * 0           - Success
  * * -EAGAIN     - Probe function has not finished yet. Try again.
  * * -EINVAL     - Invalid pmc_idx
- * * -ENODEV     - PMC device is not available
+ * * -ENODEV     - PMC device is not available (hardware absent or driver failed to initialize)
  */
 int pmc_ssram_telemetry_get_pmc_info(unsigned int pmc_idx,
 				     struct pmc_ssram_telemetry *pmc_ssram_telemetry)
 {
+	enum pmc_ssram_state state;
+
+	if (pmc_idx >= MAX_NUM_PMC)
+		return -EINVAL;
+
 	/*
 	 * PMCs are discovered in probe function. If this function is called before
-	 * probe function complete, the result would be invalid. Use device_probed
-	 * variable to avoid this case. Return -EAGAIN to inform the consumer to call
-	 * again later.
+	 * probe function complete, the result would be invalid. Use per-PMC state
+	 * to inform the consumer to call again later.
 	 */
-	if (!device_probed)
+	state = smp_load_acquire(&pmc_ssram_state[pmc_idx]);
+	if (state == PMC_SSRAM_UNPROBED || state == PMC_SSRAM_PROBING)
 		return -EAGAIN;
 
-	/*
-	 * Memory barrier is used to ensure the correct read order between
-	 * device_probed variable and PMC info.
-	 */
-	smp_rmb();
-	if (pmc_idx >= MAX_NUM_PMC)
-		return -EINVAL;
-
-	if (!pmc_ssram_telems || !pmc_ssram_telems[pmc_idx].devid)
+	if (state == PMC_SSRAM_ABSENT)
 		return -ENODEV;
 
+	/*
+	 * Acquire semantics order reads of devid and base_addr after observing
+	 * PRESENT and pair with the writer's release-store.
+	 */
 	pmc_ssram_telemetry->devid = pmc_ssram_telems[pmc_idx].devid;
 	pmc_ssram_telemetry->base_addr = pmc_ssram_telems[pmc_idx].base_addr;
 	return 0;
 }
 EXPORT_SYMBOL_GPL(pmc_ssram_telemetry_get_pmc_info);
 
+static void pmc_ssram_publish_absent_mask(unsigned long mask)
+{
+	unsigned long bit;
+
+	for_each_set_bit(bit, &mask, MAX_NUM_PMC)
+		pmc_ssram_publish_absent(bit);
+}
+
+static void pmc_ssram_publish_telems(struct pmc_ssram_probe_cache *probe_cache, int ret)
+{
+	unsigned long bit;
+
+	if (ret) {
+		pmc_ssram_publish_absent_mask(probe_cache->owned_mask);
+		return;
+	}
+
+	for_each_set_bit(bit, &probe_cache->owned_mask, MAX_NUM_PMC) {
+		if (probe_cache->valid_mask & BIT(bit))
+			pmc_ssram_publish_present(probe_cache, bit);
+		else
+			pmc_ssram_publish_absent(bit);
+	}
+}
+
 static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_device_id *id)
 {
+	struct pmc_ssram_probe_cache probe_cache = {};
+	struct pmc_ssram_drvdata *drvdata;
 	const struct ssram_type *ssram_type;
 	enum resource_method method;
 	int ret;
 
-	pmc_ssram_telems = devm_kzalloc(&pcidev->dev, sizeof(*pmc_ssram_telems) * MAX_NUM_PMC,
-					GFP_KERNEL);
-	if (!pmc_ssram_telems) {
-		ret = -ENOMEM;
-		goto probe_finish;
-	}
-
 	ssram_type = (const struct ssram_type *)id->driver_data;
 	if (!ssram_type) {
 		dev_dbg(&pcidev->dev, "missing driver data\n");
-		ret = -EINVAL;
-		goto probe_finish;
+		return -EINVAL;
 	}
 
 	method = ssram_type->method;
+	if (method == RES_METHOD_PCI)
+		probe_cache.owned_mask = SSRAM_PCI_PMC_MASK;
+	else
+		return -EINVAL;
+
+	pmc_ssram_mark_probing(probe_cache.owned_mask);
+
+	drvdata = devm_kzalloc(&pcidev->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata) {
+		ret = -ENOMEM;
+		goto probe_finish;
+	}
 
 	ret = pcim_enable_device(pcidev);
 	if (ret) {
@@ -214,20 +322,29 @@ static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_de
 	}
 
 	if (method == RES_METHOD_PCI)
-		ret = pmc_ssram_telemetry_pci_init(pcidev);
+		ret = pmc_ssram_telemetry_pci_init(pcidev, &probe_cache);
 	else
 		ret = -EINVAL;
 
+	if (!ret) {
+		drvdata->owned_mask = probe_cache.owned_mask;
+		pci_set_drvdata(pcidev, drvdata);
+	}
+
 probe_finish:
-	/*
-	 * Memory barrier is used to ensure the correct write order between PMC info
-	 * and device_probed variable.
-	 */
-	smp_wmb();
-	device_probed = true;
+	pmc_ssram_publish_telems(&probe_cache, ret);
+
 	return ret;
 }
 
+static void pmc_ssram_telemetry_remove(struct pci_dev *pcidev)
+{
+	struct pmc_ssram_drvdata *drvdata = pci_get_drvdata(pcidev);
+
+	if (drvdata)
+		pmc_ssram_publish_absent_mask(drvdata->owned_mask);
+}
+
 static const struct pci_device_id pmc_ssram_telemetry_pci_ids[] = {
 	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_MTL_SOCM),
 		.driver_data = (kernel_ulong_t)&pci_main },
@@ -251,6 +368,7 @@ static struct pci_driver pmc_ssram_telemetry_driver = {
 	.name = "intel_pmc_ssram_telemetry",
 	.id_table = pmc_ssram_telemetry_pci_ids,
 	.probe = pmc_ssram_telemetry_probe,
+	.remove = pmc_ssram_telemetry_remove,
 };
 module_pci_driver(pmc_ssram_telemetry_driver);
 
author maintainer
#14 lore raw

Re: [PATCH v7 12/15] platform/x86/intel/pmc/ssram: Switch to static array with per-index probe state

Quoted 19 lines · Ilpo Järvinen
Quoted 17 lines · David E. Box

From: Xi Pardee <[email protected]>

Replace devm-allocated pmc_ssram_telems pointer with a fixed-size static array and introduce per-index probe state tracking.

This prepares the driver for later per-device probe handling where tying the PMC tracking storage to one probed PCI device is no longer suitable.

The previous single global device_probed flag cannot describe the state of individual PMC indices when multiple devices can be probed independently. Replace it with per-index state (UNPROBED, PROBING, PRESENT, ABSENT) and a staging cache that publishes discovered values only after probe completes. This avoids races between probe/unbind and concurrent readers.

Use marked state accesses with release/acquire ordering to prevent compiler and CPU reordering issues across concurrent probe/unbind cycles.

Quoted 8 lines
Quoted 1 line

Hi,

I started to wonder is this anymore Xi's handiwork? This patch as been rewritten so totally from the earlier that I cannot really see much from the earlier remaining, so is Xi still the right author for it? (It might be correct, I just don't know who did the actual work here).

It has significantlly changed. I removed Xi's authorship and signoff but added attribution for v1 in the commit message for v8.

Quoted 95 lines
Quoted 75 lines
Assisted-by Claude:claude-sonnet-4-5

---
V7 changes:
- Made PMC_SSRAM_UNPROBED explicitly = 0 for clarity.
- Replace the explicit smp_wmb() / smp_rmb() pairing with smp_store_release() / smp_load_acquire() when publishing and consuming the per-index SSRAM state.

V6 changes:
- Squashed patch combining v5 patches 10 ("Use fixed-size static pmc array") and 13 ("Refactor memory barrier for reentrant probe"). Both patches addressed per-index probe state tracking and reentrant probe protection, so they were combined for better logical cohesion.
- Added per-index probe state enum (UNPROBED, PROBING, PRESENT, ABSENT) to replace devid overload where devid was used as both payload and probe state indicator. This fixes stale data issues on reprobe, distinguishes between -EAGAIN (probe in progress) and -ENODEV (probe failed) error semantics, and prevents stale values from being visible after failed reprobe (Ilpo/Sashiko/Claude).
- Added staging cache that publishes devid and base_addr only after probe completes successfully to avoid races between probe/unbind and concurrent readers.
- Added .remove callback to handle proper state cleanup on driver unbind.
- Used READ_ONCE/WRITE_ONCE for pmc_ssram_state[] accesses to prevent compiler optimizations from causing issues across concurrent probe/ unbind cycles.

V5 - No changes (for both original patches)

V4 - No changes (for both original patches)

V3 - No changes (for both original patches)

V2 changes (from original patch 10 "Use fixed-size static pmc array"):
- Replaced hardcoded array size [3] with MAX_NUM_PMC constant

V2 changes (from original patch 13 "Refactor memory barrier"):
- Expanded commit message to explain synchronization rationale
- Remove unused probe_finish label associated with the old global flag

 .../platform/x86/intel/pmc/ssram_telemetry.c  | 202 ++++++++++++++----
 1 file changed, 160 insertions(+), 42 deletions(-)
drivers/platform/x86/intel/pmc/ssram_telemetry.c +6 -2
index 211d842df449..110ea2dfa542 100644
@@ -5,6 +5,7 @@
  * Copyright (c) 2023, Intel Corporation.
  */
 
+#include <linux/bitmap.h>
 #include <linux/bits.h>
 #include <linux/cleanup.h>
 #include <linux/device.h>
@@ -24,6 +25,7 @@
 #define SSRAM_IOE_OFFSET	0x68
 #define SSRAM_DEVID_OFFSET	0x70
 #define SSRAM_BASE_ADDR_MASK	GENMASK_ULL(63, 3)
+#define SSRAM_PCI_PMC_MASK	(BIT(PMC_IDX_MAIN) | BIT(PMC_IDX_IOE) | BIT(PMC_IDX_PCH))
 
 DEFINE_FREE(pmc_ssram_telemetry_iounmap, void __iomem *, if (_T) iounmap(_T))
 
@@ -39,15 +41,33 @@ static const struct ssram_type pci_main = {
 	.method = RES_METHOD_PCI,
 };
 
-static struct pmc_ssram_telemetry *pmc_ssram_telems;
-static bool device_probed;
+enum pmc_ssram_state {
+	PMC_SSRAM_UNPROBED = 0,
+	PMC_SSRAM_PROBING,
+	PMC_SSRAM_PRESENT,

drivers/platform/x86/intel/pmc/ssram_telemetry.c static const struct ssram_type pci_main = { · line 57

Quoted 10 lines
drivers/platform/x86/intel/pmc/ssram_telemetry.c +10 -0
+	PMC_SSRAM_ABSENT,
+};
+
+static enum pmc_ssram_state pmc_ssram_state[MAX_NUM_PMC];
+static struct pmc_ssram_telemetry pmc_ssram_telems[MAX_NUM_PMC];
+
+struct pmc_ssram_probe_cache {
+	struct pmc_ssram_telemetry telems[MAX_NUM_PMC];
+	unsigned long owned_mask;
+	unsigned long valid_mask;

I went through this change again and as far as I could understand it, it looks okay to me.

But, could you please suggest some descriptive comments to these (or kerneldoc for the entire struct). I think it would be helpful for understanding the state tracking algorithm to have the intent for each of these stated upfront (rather than code reader having to decipher it from the code like I had to do).

Quoted 3 lines

I can try to put the comment(s) in while applying (unless you want to send an updated version of the entire series yourself which would be fine as well and save me little bit of editing effort).

I'll send a v8. Thanks Ilpo.

David

Quoted 291 lines
Signature · 2 lines
-- 
 i.
Quoted 286 lines
drivers/platform/x86/intel/pmc/ssram_telemetry.c +144 -40
+};
+
+struct pmc_ssram_drvdata {
+	unsigned long owned_mask;
+};
 
 static inline u64 get_base(void __iomem *addr, u32 offset)
 {
 	return lo_hi_readq(addr + offset) & SSRAM_BASE_ADDR_MASK;
 }
 
-static void pmc_ssram_get_devid_pwrmbase(void __iomem *ssram, unsigned int pmc_idx)
+static void pmc_ssram_get_devid_pwrmbase(struct pmc_ssram_probe_cache *probe_cache,
+					 void __iomem *ssram, unsigned int pmc_idx)
 {
 	u64 pwrm_base;
 	u16 devid;
@@ -55,8 +75,45 @@ static void pmc_ssram_get_devid_pwrmbase(void __iomem *ssram, unsigned int pmc_i
 	pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET);
 	devid = readw(ssram + SSRAM_DEVID_OFFSET);
 
-	pmc_ssram_telems[pmc_idx].devid = devid;
-	pmc_ssram_telems[pmc_idx].base_addr = pwrm_base;
+	probe_cache->telems[pmc_idx].base_addr = pwrm_base;
+	probe_cache->telems[pmc_idx].devid = devid;
+}
+
+static void pmc_ssram_publish_absent(unsigned int pmc_idx)
+{
+	/*
+	 * Publish only the state without modifying base_addr and devid. This
+	 * lets a reader that already observed PRESENT finish copying the
+	 * previous values even if unbind concurrently publishes ABSENT. Readers
+	 * that observe ABSENT return -ENODEV without accessing data.
+	 */
+	smp_store_release(&pmc_ssram_state[pmc_idx], PMC_SSRAM_ABSENT);
+}
+
+static void pmc_ssram_publish_present(struct pmc_ssram_probe_cache *probe_cache,
+				      unsigned int pmc_idx)
+{
+	/*
+	 * The devid and base_addr fields are read from hardware MMIO registers
+	 * whose values are stable for a given PMC index. A reader that observed
+	 * PRESENT from an earlier probe can safely copy them while a concurrent
+	 * rebind republishes those fields because both probes read the same
+	 * hardware values.
+	 */
+	pmc_ssram_telems[pmc_idx] = probe_cache->telems[pmc_idx];
+	/*
+	 * Publish base_addr and devid before publishing PRESENT. Pairs with the
+	 * acquire load in the reader that consumes them after observing PRESENT.
+	 */
+	smp_store_release(&pmc_ssram_state[pmc_idx], PMC_SSRAM_PRESENT);
+}
+
+static void pmc_ssram_mark_probing(unsigned long mask)
+{
+	unsigned long bit;
+
+	for_each_set_bit(bit, &mask, MAX_NUM_PMC)
+		WRITE_ONCE(pmc_ssram_state[bit], PMC_SSRAM_PROBING);
 }
 
 static int
@@ -96,11 +153,14 @@ pmc_ssram_telemetry_add_pmt(struct pci_dev *pcidev, u64 ssram_base, void __iomem
 }
 
 static int
-pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev, unsigned int pmc_idx, u32 offset)
+pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev,
+				struct pmc_ssram_probe_cache *probe_cache,
+				unsigned int pmc_idx, u32 offset)
 {
 	void __iomem __free(pmc_ssram_telemetry_iounmap) *tmp_ssram = NULL;
 	void __iomem __free(pmc_ssram_telemetry_iounmap) *ssram = NULL;
 	u64 ssram_base;
+	int ret;
 
 	ssram_base = pci_resource_start(pcidev, 0);
 	tmp_ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
@@ -125,22 +185,38 @@ pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev, unsigned int pmc_idx, u3
 		ssram = no_free_ptr(tmp_ssram);
 	}
 
-	pmc_ssram_get_devid_pwrmbase(ssram, pmc_idx);
+	pmc_ssram_get_devid_pwrmbase(probe_cache, ssram, pmc_idx);
 
 	/* Find and register and PMC telemetry entries */
-	return pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram);
+	ret = pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram);
+	if (ret)
+		return ret;
+
+	probe_cache->valid_mask |= BIT(pmc_idx);
+
+	return 0;
 }
 
-static int pmc_ssram_telemetry_pci_init(struct pci_dev *pcidev)
+static int pmc_ssram_telemetry_pci_init(struct pci_dev *pcidev,
+					struct pmc_ssram_probe_cache *probe_cache)
 {
 	int ret;
 
-	ret = pmc_ssram_telemetry_get_pmc_pci(pcidev, PMC_IDX_MAIN, 0);
+	ret = pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_MAIN, 0);
 	if (ret)
 		return ret;
 
-	pmc_ssram_telemetry_get_pmc_pci(pcidev, PMC_IDX_IOE, SSRAM_IOE_OFFSET);
-	pmc_ssram_telemetry_get_pmc_pci(pcidev, PMC_IDX_PCH, SSRAM_PCH_OFFSET);
+	/*
+	 * If MAIN PMC enumeration is successful but either IOE or PCH fail,
+	 * don't fail probe as the MAIN PMC is still useful as it provides the
+	 * global reset and slp_s0 counter access. Failed or missing secondary
+	 * PMCs are left out of valid_mask and published as absent.
+	 */
+	pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_IOE,
+					SSRAM_IOE_OFFSET);
+
+	pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_PCH,
+					SSRAM_PCH_OFFSET);
 
 	return 0;
 }
@@ -154,58 +230,90 @@ static int pmc_ssram_telemetry_pci_init(struct pci_dev *pcidev)
  * * 0           - Success
  * * -EAGAIN     - Probe function has not finished yet. Try again.
  * * -EINVAL     - Invalid pmc_idx
- * * -ENODEV     - PMC device is not available
+ * * -ENODEV     - PMC device is not available (hardware absent or driver failed to initialize)
  */
 int pmc_ssram_telemetry_get_pmc_info(unsigned int pmc_idx,
 				     struct pmc_ssram_telemetry *pmc_ssram_telemetry)
 {
+	enum pmc_ssram_state state;
+
+	if (pmc_idx >= MAX_NUM_PMC)
+		return -EINVAL;
+
 	/*
 	 * PMCs are discovered in probe function. If this function is called before
-	 * probe function complete, the result would be invalid. Use device_probed
-	 * variable to avoid this case. Return -EAGAIN to inform the consumer to call
-	 * again later.
+	 * probe function complete, the result would be invalid. Use per-PMC state
+	 * to inform the consumer to call again later.
 	 */
-	if (!device_probed)
+	state = smp_load_acquire(&pmc_ssram_state[pmc_idx]);
+	if (state == PMC_SSRAM_UNPROBED || state == PMC_SSRAM_PROBING)
 		return -EAGAIN;
 
-	/*
-	 * Memory barrier is used to ensure the correct read order between
-	 * device_probed variable and PMC info.
-	 */
-	smp_rmb();
-	if (pmc_idx >= MAX_NUM_PMC)
-		return -EINVAL;
-
-	if (!pmc_ssram_telems || !pmc_ssram_telems[pmc_idx].devid)
+	if (state == PMC_SSRAM_ABSENT)
 		return -ENODEV;
 
+	/*
+	 * Acquire semantics order reads of devid and base_addr after observing
+	 * PRESENT and pair with the writer's release-store.
+	 */
 	pmc_ssram_telemetry->devid = pmc_ssram_telems[pmc_idx].devid;
 	pmc_ssram_telemetry->base_addr = pmc_ssram_telems[pmc_idx].base_addr;
 	return 0;
 }
 EXPORT_SYMBOL_GPL(pmc_ssram_telemetry_get_pmc_info);
 
+static void pmc_ssram_publish_absent_mask(unsigned long mask)
+{
+	unsigned long bit;
+
+	for_each_set_bit(bit, &mask, MAX_NUM_PMC)
+		pmc_ssram_publish_absent(bit);
+}
+
+static void pmc_ssram_publish_telems(struct pmc_ssram_probe_cache *probe_cache, int ret)
+{
+	unsigned long bit;
+
+	if (ret) {
+		pmc_ssram_publish_absent_mask(probe_cache->owned_mask);
+		return;
+	}
+
+	for_each_set_bit(bit, &probe_cache->owned_mask, MAX_NUM_PMC) {
+		if (probe_cache->valid_mask & BIT(bit))
+			pmc_ssram_publish_present(probe_cache, bit);
+		else
+			pmc_ssram_publish_absent(bit);
+	}
+}
+
 static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_device_id *id)
 {
+	struct pmc_ssram_probe_cache probe_cache = {};
+	struct pmc_ssram_drvdata *drvdata;
 	const struct ssram_type *ssram_type;
 	enum resource_method method;
 	int ret;
 
-	pmc_ssram_telems = devm_kzalloc(&pcidev->dev, sizeof(*pmc_ssram_telems) * MAX_NUM_PMC,
-					GFP_KERNEL);
-	if (!pmc_ssram_telems) {
-		ret = -ENOMEM;
-		goto probe_finish;
-	}
-
 	ssram_type = (const struct ssram_type *)id->driver_data;
 	if (!ssram_type) {
 		dev_dbg(&pcidev->dev, "missing driver data\n");
-		ret = -EINVAL;
-		goto probe_finish;
+		return -EINVAL;
 	}
 
 	method = ssram_type->method;
+	if (method == RES_METHOD_PCI)
+		probe_cache.owned_mask = SSRAM_PCI_PMC_MASK;
+	else
+		return -EINVAL;
+
+	pmc_ssram_mark_probing(probe_cache.owned_mask);
+
+	drvdata = devm_kzalloc(&pcidev->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata) {
+		ret = -ENOMEM;
+		goto probe_finish;
+	}
 
 	ret = pcim_enable_device(pcidev);
 	if (ret) {
@@ -214,20 +322,29 @@ static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_de
 	}
 
 	if (method == RES_METHOD_PCI)
-		ret = pmc_ssram_telemetry_pci_init(pcidev);
+		ret = pmc_ssram_telemetry_pci_init(pcidev, &probe_cache);
 	else
 		ret = -EINVAL;
 
+	if (!ret) {
+		drvdata->owned_mask = probe_cache.owned_mask;
+		pci_set_drvdata(pcidev, drvdata);
+	}
+
 probe_finish:
-	/*
-	 * Memory barrier is used to ensure the correct write order between PMC info
-	 * and device_probed variable.
-	 */
-	smp_wmb();
-	device_probed = true;
+	pmc_ssram_publish_telems(&probe_cache, ret);
+
 	return ret;
 }
 
+static void pmc_ssram_telemetry_remove(struct pci_dev *pcidev)
+{
+	struct pmc_ssram_drvdata *drvdata = pci_get_drvdata(pcidev);
+
+	if (drvdata)
+		pmc_ssram_publish_absent_mask(drvdata->owned_mask);
+}
+
 static const struct pci_device_id pmc_ssram_telemetry_pci_ids[] = {
 	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_MTL_SOCM),
 		.driver_data = (kernel_ulong_t)&pci_main },
@@ -251,6 +368,7 @@ static struct pci_driver pmc_ssram_telemetry_driver = {
 	.name = "intel_pmc_ssram_telemetry",
 	.id_table = pmc_ssram_telemetry_pci_ids,
 	.probe = pmc_ssram_telemetry_probe,
+	.remove = pmc_ssram_telemetry_remove,
 };
 module_pci_driver(pmc_ssram_telemetry_driver);