Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

platform/x86: Intel PMT class driver

Intel Platform Monitoring Technology is meant to provide a common way to
access telemetry and system metrics.

Register mappings are not provided by the driver. Instead, a GUID is read
from a header for each endpoint. The GUID identifies the device and is to
be used with an XML, provided by the vendor, to discover the available set
of metrics and their register mapping. This allows firmware updates to
modify the register space without needing to update the driver every time
with new mappings. Firmware writes a new GUID in this case to specify the
new mapping. Software tools with access to the associated XML file can
then interpret the changes.

The module manages access to all Intel PMT endpoints on a system,
independent of the device exporting them. It creates an intel_pmt class
to manage the devices. For each telemetry endpoint, sysfs files provide
GUID and size information as well as a pointer to the parent device the
telemetry came from. Software may discover the association between
endpoints and devices by iterating through the list in sysfs, or by looking
for the existence of the class folder under the device of interest. A
binary sysfs attribute of the same name allows software to then read or map
the telemetry space for direct access.

Signed-off-by: Alexander Duyck <alexander.h.duyck@linux.intel.com>
Signed-off-by: David E. Box <david.e.box@linux.intel.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Lee Jones <lee.jones@linaro.org>

authored by

Alexander Duyck and committed by
Lee Jones
e2729113 4f8217d5

+417
+54
Documentation/ABI/testing/sysfs-class-intel_pmt
··· 1 + What: /sys/class/intel_pmt/ 2 + Date: October 2020 3 + KernelVersion: 5.10 4 + Contact: David Box <david.e.box@linux.intel.com> 5 + Description: 6 + The intel_pmt/ class directory contains information for 7 + devices that expose hardware telemetry using Intel Platform 8 + Monitoring Technology (PMT) 9 + 10 + What: /sys/class/intel_pmt/telem<x> 11 + Date: October 2020 12 + KernelVersion: 5.10 13 + Contact: David Box <david.e.box@linux.intel.com> 14 + Description: 15 + The telem<x> directory contains files describing an instance of 16 + a PMT telemetry device that exposes hardware telemetry. Each 17 + telem<x> directory has an associated telem file. This file 18 + may be opened and mapped or read to access the telemetry space 19 + of the device. The register layout of the telemetry space is 20 + determined from an XML file that matches the PCI device id and 21 + GUID for the device. 22 + 23 + What: /sys/class/intel_pmt/telem<x>/telem 24 + Date: October 2020 25 + KernelVersion: 5.10 26 + Contact: David Box <david.e.box@linux.intel.com> 27 + Description: 28 + (RO) The telemetry data for this telemetry device. This file 29 + may be mapped or read to obtain the data. 30 + 31 + What: /sys/class/intel_pmt/telem<x>/guid 32 + Date: October 2020 33 + KernelVersion: 5.10 34 + Contact: David Box <david.e.box@linux.intel.com> 35 + Description: 36 + (RO) The GUID for this telemetry device. The GUID identifies 37 + the version of the XML file for the parent device that is to 38 + be used to get the register layout. 39 + 40 + What: /sys/class/intel_pmt/telem<x>/size 41 + Date: October 2020 42 + KernelVersion: 5.10 43 + Contact: David Box <david.e.box@linux.intel.com> 44 + Description: 45 + (RO) The size of telemetry region in bytes that corresponds to 46 + the mapping size for the telem file. 47 + 48 + What: /sys/class/intel_pmt/telem<x>/offset 49 + Date: October 2020 50 + KernelVersion: 5.10 51 + Contact: David Box <david.e.box@linux.intel.com> 52 + Description: 53 + (RO) The offset of telemetry region in bytes that corresponds to 54 + the mapping for the telem file.
+1
MAINTAINERS
··· 9025 9025 M: "David E. Box" <david.e.box@linux.intel.com> 9026 9026 S: Maintained 9027 9027 F: drivers/mfd/intel_pmt.c 9028 + F: drivers/platform/x86/intel_pmt_* 9028 9029 9029 9030 INTEL PRO/WIRELESS 2100, 2200BG, 2915ABG NETWORK CONNECTION SUPPORT 9030 9031 M: Stanislav Yakovlev <stas.yakovlev@gmail.com>
+12
drivers/platform/x86/Kconfig
··· 1362 1362 - LTR Ignore 1363 1363 - MPHY/PLL gating status (Sunrisepoint PCH only) 1364 1364 1365 + config INTEL_PMT_CLASS 1366 + tristate "Intel Platform Monitoring Technology (PMT) Class driver" 1367 + help 1368 + The Intel Platform Monitoring Technology (PMT) class driver provides 1369 + the basic sysfs interface and file hierarchy uses by PMT devices. 1370 + 1371 + For more information, see: 1372 + <file:Documentation/ABI/testing/sysfs-class-intel_pmt> 1373 + 1374 + To compile this driver as a module, choose M here: the module 1375 + will be called intel_pmt_class. 1376 + 1365 1377 config INTEL_PUNIT_IPC 1366 1378 tristate "Intel P-Unit IPC Driver" 1367 1379 help
+1
drivers/platform/x86/Makefile
··· 140 140 obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o 141 141 obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o 142 142 obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o 143 + obj-$(CONFIG_INTEL_PMT_CLASS) += intel_pmt_class.o 143 144 obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o 144 145 obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o 145 146 obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o
+297
drivers/platform/x86/intel_pmt_class.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Intel Platform Monitory Technology Telemetry driver 4 + * 5 + * Copyright (c) 2020, Intel Corporation. 6 + * All Rights Reserved. 7 + * 8 + * Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com> 9 + */ 10 + 11 + #include <linux/kernel.h> 12 + #include <linux/module.h> 13 + #include <linux/mm.h> 14 + #include <linux/pci.h> 15 + 16 + #include "intel_pmt_class.h" 17 + 18 + #define PMT_XA_START 0 19 + #define PMT_XA_MAX INT_MAX 20 + #define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX) 21 + 22 + /* 23 + * sysfs 24 + */ 25 + static ssize_t 26 + intel_pmt_read(struct file *filp, struct kobject *kobj, 27 + struct bin_attribute *attr, char *buf, loff_t off, 28 + size_t count) 29 + { 30 + struct intel_pmt_entry *entry = container_of(attr, 31 + struct intel_pmt_entry, 32 + pmt_bin_attr); 33 + 34 + if (off < 0) 35 + return -EINVAL; 36 + 37 + if (off >= entry->size) 38 + return 0; 39 + 40 + if (count > entry->size - off) 41 + count = entry->size - off; 42 + 43 + memcpy_fromio(buf, entry->base + off, count); 44 + 45 + return count; 46 + } 47 + 48 + static int 49 + intel_pmt_mmap(struct file *filp, struct kobject *kobj, 50 + struct bin_attribute *attr, struct vm_area_struct *vma) 51 + { 52 + struct intel_pmt_entry *entry = container_of(attr, 53 + struct intel_pmt_entry, 54 + pmt_bin_attr); 55 + unsigned long vsize = vma->vm_end - vma->vm_start; 56 + struct device *dev = kobj_to_dev(kobj); 57 + unsigned long phys = entry->base_addr; 58 + unsigned long pfn = PFN_DOWN(phys); 59 + unsigned long psize; 60 + 61 + if (vma->vm_flags & (VM_WRITE | VM_MAYWRITE)) 62 + return -EROFS; 63 + 64 + psize = (PFN_UP(entry->base_addr + entry->size) - pfn) * PAGE_SIZE; 65 + if (vsize > psize) { 66 + dev_err(dev, "Requested mmap size is too large\n"); 67 + return -EINVAL; 68 + } 69 + 70 + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); 71 + if (io_remap_pfn_range(vma, vma->vm_start, pfn, 72 + vsize, vma->vm_page_prot)) 73 + return -EAGAIN; 74 + 75 + return 0; 76 + } 77 + 78 + static ssize_t 79 + guid_show(struct device *dev, struct device_attribute *attr, char *buf) 80 + { 81 + struct intel_pmt_entry *entry = dev_get_drvdata(dev); 82 + 83 + return sprintf(buf, "0x%x\n", entry->guid); 84 + } 85 + static DEVICE_ATTR_RO(guid); 86 + 87 + static ssize_t size_show(struct device *dev, struct device_attribute *attr, 88 + char *buf) 89 + { 90 + struct intel_pmt_entry *entry = dev_get_drvdata(dev); 91 + 92 + return sprintf(buf, "%zu\n", entry->size); 93 + } 94 + static DEVICE_ATTR_RO(size); 95 + 96 + static ssize_t 97 + offset_show(struct device *dev, struct device_attribute *attr, char *buf) 98 + { 99 + struct intel_pmt_entry *entry = dev_get_drvdata(dev); 100 + 101 + return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr)); 102 + } 103 + static DEVICE_ATTR_RO(offset); 104 + 105 + static struct attribute *intel_pmt_attrs[] = { 106 + &dev_attr_guid.attr, 107 + &dev_attr_size.attr, 108 + &dev_attr_offset.attr, 109 + NULL 110 + }; 111 + ATTRIBUTE_GROUPS(intel_pmt); 112 + 113 + static struct class intel_pmt_class = { 114 + .name = "intel_pmt", 115 + .owner = THIS_MODULE, 116 + .dev_groups = intel_pmt_groups, 117 + }; 118 + 119 + static int intel_pmt_populate_entry(struct intel_pmt_entry *entry, 120 + struct intel_pmt_header *header, 121 + struct device *dev, 122 + struct resource *disc_res) 123 + { 124 + struct pci_dev *pci_dev = to_pci_dev(dev->parent); 125 + u8 bir; 126 + 127 + /* 128 + * The base offset should always be 8 byte aligned. 129 + * 130 + * For non-local access types the lower 3 bits of base offset 131 + * contains the index of the base address register where the 132 + * telemetry can be found. 133 + */ 134 + bir = GET_BIR(header->base_offset); 135 + 136 + /* Local access and BARID only for now */ 137 + switch (header->access_type) { 138 + case ACCESS_LOCAL: 139 + if (bir) { 140 + dev_err(dev, 141 + "Unsupported BAR index %d for access type %d\n", 142 + bir, header->access_type); 143 + return -EINVAL; 144 + } 145 + /* 146 + * For access_type LOCAL, the base address is as follows: 147 + * base address = end of discovery region + base offset 148 + */ 149 + entry->base_addr = disc_res->end + 1 + header->base_offset; 150 + break; 151 + case ACCESS_BARID: 152 + /* 153 + * If another BAR was specified then the base offset 154 + * represents the offset within that BAR. SO retrieve the 155 + * address from the parent PCI device and add offset. 156 + */ 157 + entry->base_addr = pci_resource_start(pci_dev, bir) + 158 + GET_ADDRESS(header->base_offset); 159 + break; 160 + default: 161 + dev_err(dev, "Unsupported access type %d\n", 162 + header->access_type); 163 + return -EINVAL; 164 + } 165 + 166 + entry->guid = header->guid; 167 + entry->size = header->size; 168 + 169 + return 0; 170 + } 171 + 172 + static int intel_pmt_dev_register(struct intel_pmt_entry *entry, 173 + struct intel_pmt_namespace *ns, 174 + struct device *parent) 175 + { 176 + struct resource res; 177 + struct device *dev; 178 + int ret; 179 + 180 + ret = xa_alloc(ns->xa, &entry->devid, entry, PMT_XA_LIMIT, GFP_KERNEL); 181 + if (ret) 182 + return ret; 183 + 184 + dev = device_create(&intel_pmt_class, parent, MKDEV(0, 0), entry, 185 + "%s%d", ns->name, entry->devid); 186 + 187 + if (IS_ERR(dev)) { 188 + dev_err(parent, "Could not create %s%d device node\n", 189 + ns->name, entry->devid); 190 + ret = PTR_ERR(dev); 191 + goto fail_dev_create; 192 + } 193 + 194 + entry->kobj = &dev->kobj; 195 + 196 + if (ns->attr_grp) { 197 + ret = sysfs_create_group(entry->kobj, ns->attr_grp); 198 + if (ret) 199 + goto fail_sysfs; 200 + } 201 + 202 + /* if size is 0 assume no data buffer, so no file needed */ 203 + if (!entry->size) 204 + return 0; 205 + 206 + res.start = entry->base_addr; 207 + res.end = res.start + entry->size - 1; 208 + res.flags = IORESOURCE_MEM; 209 + 210 + entry->base = devm_ioremap_resource(dev, &res); 211 + if (IS_ERR(entry->base)) { 212 + ret = PTR_ERR(entry->base); 213 + goto fail_ioremap; 214 + } 215 + 216 + sysfs_bin_attr_init(&entry->pmt_bin_attr); 217 + entry->pmt_bin_attr.attr.name = ns->name; 218 + entry->pmt_bin_attr.attr.mode = 0440; 219 + entry->pmt_bin_attr.mmap = intel_pmt_mmap; 220 + entry->pmt_bin_attr.read = intel_pmt_read; 221 + entry->pmt_bin_attr.size = entry->size; 222 + 223 + ret = sysfs_create_bin_file(&dev->kobj, &entry->pmt_bin_attr); 224 + if (!ret) 225 + return 0; 226 + 227 + fail_ioremap: 228 + sysfs_remove_group(entry->kobj, ns->attr_grp); 229 + fail_sysfs: 230 + device_unregister(dev); 231 + fail_dev_create: 232 + xa_erase(ns->xa, entry->devid); 233 + 234 + return ret; 235 + } 236 + 237 + int intel_pmt_dev_create(struct intel_pmt_entry *entry, 238 + struct intel_pmt_namespace *ns, 239 + struct platform_device *pdev, int idx) 240 + { 241 + struct intel_pmt_header header; 242 + struct resource *disc_res; 243 + int ret = -ENODEV; 244 + 245 + disc_res = platform_get_resource(pdev, IORESOURCE_MEM, idx); 246 + if (!disc_res) 247 + return ret; 248 + 249 + entry->disc_table = devm_platform_ioremap_resource(pdev, idx); 250 + if (IS_ERR(entry->disc_table)) 251 + return PTR_ERR(entry->disc_table); 252 + 253 + ret = ns->pmt_header_decode(entry, &header, &pdev->dev); 254 + if (ret) 255 + return ret; 256 + 257 + ret = intel_pmt_populate_entry(entry, &header, &pdev->dev, disc_res); 258 + if (ret) 259 + return ret; 260 + 261 + return intel_pmt_dev_register(entry, ns, &pdev->dev); 262 + 263 + } 264 + EXPORT_SYMBOL_GPL(intel_pmt_dev_create); 265 + 266 + void intel_pmt_dev_destroy(struct intel_pmt_entry *entry, 267 + struct intel_pmt_namespace *ns) 268 + { 269 + struct device *dev = kobj_to_dev(entry->kobj); 270 + 271 + if (entry->size) 272 + sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr); 273 + 274 + if (ns->attr_grp) 275 + sysfs_remove_group(entry->kobj, ns->attr_grp); 276 + 277 + device_unregister(dev); 278 + xa_erase(ns->xa, entry->devid); 279 + } 280 + EXPORT_SYMBOL_GPL(intel_pmt_dev_destroy); 281 + 282 + static int __init pmt_class_init(void) 283 + { 284 + return class_register(&intel_pmt_class); 285 + } 286 + 287 + static void __exit pmt_class_exit(void) 288 + { 289 + class_unregister(&intel_pmt_class); 290 + } 291 + 292 + module_init(pmt_class_init); 293 + module_exit(pmt_class_exit); 294 + 295 + MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>"); 296 + MODULE_DESCRIPTION("Intel PMT Class driver"); 297 + MODULE_LICENSE("GPL v2");
+52
drivers/platform/x86/intel_pmt_class.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + #ifndef _INTEL_PMT_CLASS_H 3 + #define _INTEL_PMT_CLASS_H 4 + 5 + #include <linux/platform_device.h> 6 + #include <linux/xarray.h> 7 + #include <linux/types.h> 8 + #include <linux/bits.h> 9 + #include <linux/err.h> 10 + #include <linux/io.h> 11 + 12 + /* PMT access types */ 13 + #define ACCESS_BARID 2 14 + #define ACCESS_LOCAL 3 15 + 16 + /* PMT discovery base address/offset register layout */ 17 + #define GET_BIR(v) ((v) & GENMASK(2, 0)) 18 + #define GET_ADDRESS(v) ((v) & GENMASK(31, 3)) 19 + 20 + struct intel_pmt_entry { 21 + struct bin_attribute pmt_bin_attr; 22 + struct kobject *kobj; 23 + void __iomem *disc_table; 24 + void __iomem *base; 25 + unsigned long base_addr; 26 + size_t size; 27 + u32 guid; 28 + int devid; 29 + }; 30 + 31 + struct intel_pmt_header { 32 + u32 base_offset; 33 + u32 size; 34 + u32 guid; 35 + u8 access_type; 36 + }; 37 + 38 + struct intel_pmt_namespace { 39 + const char *name; 40 + struct xarray *xa; 41 + const struct attribute_group *attr_grp; 42 + int (*pmt_header_decode)(struct intel_pmt_entry *entry, 43 + struct intel_pmt_header *header, 44 + struct device *dev); 45 + }; 46 + 47 + int intel_pmt_dev_create(struct intel_pmt_entry *entry, 48 + struct intel_pmt_namespace *ns, 49 + struct platform_device *pdev, int idx); 50 + void intel_pmt_dev_destroy(struct intel_pmt_entry *entry, 51 + struct intel_pmt_namespace *ns); 52 + #endif