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. Signed-off-by: David E. Box <[email protected]> --- drivers/platform/x86/intel/pmc/Kconfig | 14 ++ drivers/platform/x86/intel/pmc/Makefile | 2 + drivers/platform/x86/intel/pmc/core.h | 12 + .../platform/x86/intel/pmc/pwrm_telemetry.c | 227 ++++++++++++++++++ 4 files changed, 255 insertions(+) create mode 100644 drivers/platform/x86/intel/pmc/pwrm_telemetry.c diff --git a/drivers/platform/x86/intel/pmc/Kconfig b/drivers/platform/x86/intel/pmc/Kconfig index 0f19dc7edcf9..937186b0b5dd 100644 --- a/drivers/platform/x86/intel/pmc/Kconfig +++ b/drivers/platform/x86/intel/pmc/Kconfig @@ -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. diff --git a/drivers/platform/x86/intel/pmc/Makefile b/drivers/platform/x86/intel/pmc/Makefile index bb960c8721d7..fdbb768f7b09 100644 --- a/drivers/platform/x86/intel/pmc/Makefile +++ b/drivers/platform/x86/intel/pmc/Makefile @@ -12,3 +12,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 diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h index 118c8740ad3a..284aced99f72 100644 --- a/drivers/platform/x86/intel/pmc/core.h +++ b/drivers/platform/x86/intel/pmc/core.h @@ -562,6 +562,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, \ @@ -583,4 +585,14 @@ 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)[4]; + +int pmc_parse_telem_dsd(union acpi_object *obj, + struct intel_vsec_header *header, + acpi_disc_t *acpi_disc); +union acpi_object *pmc_find_telem_guid(union acpi_object *dsd); #endif /* PMC_CORE_H */ diff --git a/drivers/platform/x86/intel/pmc/pwrm_telemetry.c b/drivers/platform/x86/intel/pmc/pwrm_telemetry.c new file mode 100644 index 000000000000..25ca6979c214 --- /dev/null +++ b/drivers/platform/x86/intel/pmc/pwrm_telemetry.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel PMC PWRM ACPI driver + * + * Copyright (C) 2025, Intel Corporation + */ + +#include <linux/acpi.h> +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/intel_vsec.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/resource.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/uuid.h> + +#include "core.h" + +#define ENTRY_LEN 5 + +/* DWORD2 */ +#define DVSEC_ID_MASK GENMASK(15, 0) +#define NUM_ENTRIES_MASK GENMASK(23, 16) +#define ENTRY_SIZE_MASK GENMASK(31, 24) + +/* DWORD3 */ +#define TBIR_MASK GENMASK(2, 0) +#define DISC_TBL_OFF_MASK GENMASK(31, 3) + +const guid_t intel_vsec_guid = + GUID_INIT(0x294903fb, 0x634d, 0x4fc7, 0xaf, 0x1f, 0x0f, 0xb9, + 0x56, 0xb0, 0x4f, 0xc1); + +static bool is_valid_entry(union acpi_object *pkg) +{ + int i; + + if (!pkg || pkg->type != ACPI_TYPE_PACKAGE || pkg->package.count != ENTRY_LEN) + return false; + + if (pkg->package.elements[0].type != ACPI_TYPE_STRING) + return false; + + for (i = 1; i < ENTRY_LEN; i++) + if (pkg->package.elements[i].type != ACPI_TYPE_INTEGER) + return false; + + return true; +} + +int pmc_parse_telem_dsd(union acpi_object *obj, + struct intel_vsec_header *header, + u32 (**acpi_disc)[4]) +{ + union acpi_object *vsec_pkg; + union acpi_object *disc_pkg; + u64 hdr0; + u64 hdr1; + int num_regions; + int i; + + if (!header) + return -EINVAL; + + if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 2) + return -EINVAL; + + /* First Package is DVSEC info */ + vsec_pkg = &obj->package.elements[0]; + if (!is_valid_entry(vsec_pkg)) + return -EINVAL; + + hdr0 = vsec_pkg->package.elements[3].integer.value; + hdr1 = vsec_pkg->package.elements[4].integer.value; + + header->id = FIELD_GET(DVSEC_ID_MASK, hdr0); + header->num_entries = FIELD_GET(NUM_ENTRIES_MASK, hdr0); + header->entry_size = FIELD_GET(ENTRY_SIZE_MASK, hdr0); + header->tbir = FIELD_GET(TBIR_MASK, hdr1); + header->offset = FIELD_GET(DISC_TBL_OFF_MASK, hdr1); + + /* Second Package contains the discovery tables */ + disc_pkg = &obj->package.elements[1]; + if (disc_pkg->type != ACPI_TYPE_PACKAGE || disc_pkg->package.count < 1) + return -EINVAL; + + num_regions = disc_pkg->package.count; + if (header->num_entries != num_regions) + return -EINVAL; + + *acpi_disc = kmalloc_array(num_regions, sizeof(**acpi_disc), GFP_KERNEL); + if (!*acpi_disc) + return -ENOMEM; + + for (i = 0; i < num_regions; i++) { + union acpi_object *pkg; + u64 value; + int j; + + pkg = &disc_pkg->package.elements[i]; + if (!is_valid_entry(pkg)) { + kfree(*acpi_disc); + return -EINVAL; + } + + /* Element 0 is a descriptive string; DWORD values start at index 1. */ + for (j = 1; j < ENTRY_LEN; j++) { + value = pkg->package.elements[j].integer.value; + if (value > U32_MAX) { + kfree(*acpi_disc); + return -ERANGE; + } + + (*acpi_disc)[i][j - 1] = value; + } + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(pmc_parse_telem_dsd, "INTEL_PMC_CORE"); + +union acpi_object *pmc_find_telem_guid(union acpi_object *dsd) +{ + int i; + + if (!dsd || dsd->type != ACPI_TYPE_PACKAGE) + return NULL; + + for (i = 0; i + 1 < dsd->package.count; i += 2) { + union acpi_object *uuid_obj, *data_obj; + guid_t uuid; + + uuid_obj = &dsd->package.elements[i]; + data_obj = &dsd->package.elements[i + 1]; + + if (uuid_obj->type != ACPI_TYPE_BUFFER || + uuid_obj->buffer.length != 16) + continue; + + memcpy(&uuid, uuid_obj->buffer.pointer, 16); + if (guid_equal(&uuid, &intel_vsec_guid)) + return data_obj; + } + + return NULL; +} +EXPORT_SYMBOL_NS_GPL(pmc_find_telem_guid, "INTEL_PMC_CORE"); + +static int pmc_pwrm_acpi_probe(struct platform_device *pdev) +{ + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_handle handle = ACPI_HANDLE(&pdev->dev); + struct intel_vsec_header header; + struct intel_vsec_header *headers[2] = { &header, NULL }; + struct intel_vsec_platform_info info = { }; + struct device *dev = &pdev->dev; + struct resource *res; + u32 (*acpi_disc)[4]; + union acpi_object *dsd; + acpi_status status; + int ret; + + if (!handle) + return -ENODEV; + + status = acpi_evaluate_object(handle, "_DSD", NULL, &buf); + if (ACPI_FAILURE(status)) + return dev_err_probe(dev, -ENODEV, "Could not evaluate _DSD: %s\n", + acpi_format_exception(status)); + + dsd = pmc_find_telem_guid(buf.pointer); + if (!dsd) { + ret = -ENODEV; + goto cleanup_acpi_buf; + } + + ret = pmc_parse_telem_dsd(dsd, &header, &acpi_disc); + if (ret) + goto cleanup_acpi_buf; + + res = platform_get_resource(pdev, IORESOURCE_MEM, header.tbir); + if (!res) { + ret = -EINVAL; + goto cleanup_acpi_disc; + } + + info.headers = headers; + info.caps = VSEC_CAP_TELEMETRY; + info.acpi_disc = acpi_disc; + info.src = INTEL_VSEC_DISC_ACPI; + info.base_addr = res->start; + + ret = intel_vsec_register(&pdev->dev, &info); + +cleanup_acpi_disc: + kfree(acpi_disc); +cleanup_acpi_buf: + ACPI_FREE(buf.pointer); + + return ret; +} + +static const struct acpi_device_id pmc_pwrm_acpi_ids[] = { + { "INTC1122", 0 }, /* Nova Lake */ + { "INTC1129", 0 }, /* Nova Lake */ + { } +}; +MODULE_DEVICE_TABLE(acpi, pmc_pwrm_acpi_ids); + +static struct platform_driver pmc_pwrm_acpi_driver = { + .probe = pmc_pwrm_acpi_probe, + .driver = { + .name = "intel_pmc_pwrm_acpi", + .acpi_match_table = ACPI_PTR(pmc_pwrm_acpi_ids), + }, +}; +module_platform_driver(pmc_pwrm_acpi_driver); + +MODULE_AUTHOR("David E. Box <[email protected]>"); +MODULE_DESCRIPTION("Intel PMC PWRM ACPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("INTEL_VSEC"); -- 2.43.0
