394 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Intel Platform Monitory Technology Telemetry driver4 *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/intel_vsec.h>13#include <linux/io-64-nonatomic-lo-hi.h>14#include <linux/module.h>15#include <linux/mm.h>16#include <linux/pci.h>17 18#include "class.h"19 20#define PMT_XA_START 121#define PMT_XA_MAX INT_MAX22#define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX)23#define GUID_SPR_PUNIT 0x9956f43f24 25bool intel_pmt_is_early_client_hw(struct device *dev)26{27 struct intel_vsec_device *ivdev = dev_to_ivdev(dev);28 29 /*30 * Early implementations of PMT on client platforms have some31 * differences from the server platforms (which use the Out Of Band32 * Management Services Module OOBMSM).33 */34 return !!(ivdev->quirks & VSEC_QUIRK_EARLY_HW);35}36EXPORT_SYMBOL_NS_GPL(intel_pmt_is_early_client_hw, INTEL_PMT);37 38static inline int39pmt_memcpy64_fromio(void *to, const u64 __iomem *from, size_t count)40{41 int i, remain;42 u64 *buf = to;43 44 if (!IS_ALIGNED((unsigned long)from, 8))45 return -EFAULT;46 47 for (i = 0; i < count/8; i++)48 buf[i] = readq(&from[i]);49 50 /* Copy any remaining bytes */51 remain = count % 8;52 if (remain) {53 u64 tmp = readq(&from[i]);54 55 memcpy(&buf[i], &tmp, remain);56 }57 58 return count;59}60 61int pmt_telem_read_mmio(struct pci_dev *pdev, struct pmt_callbacks *cb, u32 guid, void *buf,62 void __iomem *addr, u32 count)63{64 if (cb && cb->read_telem)65 return cb->read_telem(pdev, guid, buf, count);66 67 if (guid == GUID_SPR_PUNIT)68 /* PUNIT on SPR only supports aligned 64-bit read */69 return pmt_memcpy64_fromio(buf, addr, count);70 71 memcpy_fromio(buf, addr, count);72 73 return count;74}75EXPORT_SYMBOL_NS_GPL(pmt_telem_read_mmio, INTEL_PMT);76 77/*78 * sysfs79 */80static ssize_t81intel_pmt_read(struct file *filp, struct kobject *kobj,82 struct bin_attribute *attr, char *buf, loff_t off,83 size_t count)84{85 struct intel_pmt_entry *entry = container_of(attr,86 struct intel_pmt_entry,87 pmt_bin_attr);88 89 if (off < 0)90 return -EINVAL;91 92 if (off >= entry->size)93 return 0;94 95 if (count > entry->size - off)96 count = entry->size - off;97 98 count = pmt_telem_read_mmio(entry->ep->pcidev, entry->cb, entry->header.guid, buf,99 entry->base + off, count);100 101 return count;102}103 104static int105intel_pmt_mmap(struct file *filp, struct kobject *kobj,106 struct bin_attribute *attr, struct vm_area_struct *vma)107{108 struct intel_pmt_entry *entry = container_of(attr,109 struct intel_pmt_entry,110 pmt_bin_attr);111 unsigned long vsize = vma->vm_end - vma->vm_start;112 struct device *dev = kobj_to_dev(kobj);113 unsigned long phys = entry->base_addr;114 unsigned long pfn = PFN_DOWN(phys);115 unsigned long psize;116 117 if (vma->vm_flags & (VM_WRITE | VM_MAYWRITE))118 return -EROFS;119 120 psize = (PFN_UP(entry->base_addr + entry->size) - pfn) * PAGE_SIZE;121 if (vsize > psize) {122 dev_err(dev, "Requested mmap size is too large\n");123 return -EINVAL;124 }125 126 vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);127 if (io_remap_pfn_range(vma, vma->vm_start, pfn,128 vsize, vma->vm_page_prot))129 return -EAGAIN;130 131 return 0;132}133 134static ssize_t135guid_show(struct device *dev, struct device_attribute *attr, char *buf)136{137 struct intel_pmt_entry *entry = dev_get_drvdata(dev);138 139 return sprintf(buf, "0x%x\n", entry->guid);140}141static DEVICE_ATTR_RO(guid);142 143static ssize_t size_show(struct device *dev, struct device_attribute *attr,144 char *buf)145{146 struct intel_pmt_entry *entry = dev_get_drvdata(dev);147 148 return sprintf(buf, "%zu\n", entry->size);149}150static DEVICE_ATTR_RO(size);151 152static ssize_t153offset_show(struct device *dev, struct device_attribute *attr, char *buf)154{155 struct intel_pmt_entry *entry = dev_get_drvdata(dev);156 157 return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr));158}159static DEVICE_ATTR_RO(offset);160 161static struct attribute *intel_pmt_attrs[] = {162 &dev_attr_guid.attr,163 &dev_attr_size.attr,164 &dev_attr_offset.attr,165 NULL166};167ATTRIBUTE_GROUPS(intel_pmt);168 169static struct class intel_pmt_class = {170 .name = "intel_pmt",171 .dev_groups = intel_pmt_groups,172};173 174static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,175 struct intel_vsec_device *ivdev,176 struct resource *disc_res)177{178 struct pci_dev *pci_dev = ivdev->pcidev;179 struct device *dev = &ivdev->auxdev.dev;180 struct intel_pmt_header *header = &entry->header;181 u8 bir;182 183 /*184 * The base offset should always be 8 byte aligned.185 *186 * For non-local access types the lower 3 bits of base offset187 * contains the index of the base address register where the188 * telemetry can be found.189 */190 bir = GET_BIR(header->base_offset);191 192 /* Local access and BARID only for now */193 switch (header->access_type) {194 case ACCESS_LOCAL:195 if (bir) {196 dev_err(dev,197 "Unsupported BAR index %d for access type %d\n",198 bir, header->access_type);199 return -EINVAL;200 }201 /*202 * For access_type LOCAL, the base address is as follows:203 * base address = end of discovery region + base offset204 */205 entry->base_addr = disc_res->end + 1 + header->base_offset;206 207 /*208 * Some hardware use a different calculation for the base address209 * when access_type == ACCESS_LOCAL. On the these systems210 * ACCCESS_LOCAL refers to an address in the same BAR as the211 * header but at a fixed offset. But as the header address was212 * supplied to the driver, we don't know which BAR it was in.213 * So search for the bar whose range includes the header address.214 */215 if (intel_pmt_is_early_client_hw(dev)) {216 int i;217 218 entry->base_addr = 0;219 for (i = 0; i < 6; i++)220 if (disc_res->start >= pci_resource_start(pci_dev, i) &&221 (disc_res->start <= pci_resource_end(pci_dev, i))) {222 entry->base_addr = pci_resource_start(pci_dev, i) +223 header->base_offset;224 break;225 }226 if (!entry->base_addr)227 return -EINVAL;228 }229 230 break;231 case ACCESS_BARID:232 /* Use the provided base address if it exists */233 if (ivdev->base_addr) {234 entry->base_addr = ivdev->base_addr +235 GET_ADDRESS(header->base_offset);236 break;237 }238 239 /*240 * If another BAR was specified then the base offset241 * represents the offset within that BAR. SO retrieve the242 * address from the parent PCI device and add offset.243 */244 entry->base_addr = pci_resource_start(pci_dev, bir) +245 GET_ADDRESS(header->base_offset);246 break;247 default:248 dev_err(dev, "Unsupported access type %d\n",249 header->access_type);250 return -EINVAL;251 }252 253 entry->guid = header->guid;254 entry->size = header->size;255 entry->cb = ivdev->priv_data;256 257 return 0;258}259 260static int intel_pmt_dev_register(struct intel_pmt_entry *entry,261 struct intel_pmt_namespace *ns,262 struct device *parent)263{264 struct intel_vsec_device *ivdev = dev_to_ivdev(parent);265 struct resource res = {0};266 struct device *dev;267 int ret;268 269 ret = xa_alloc(ns->xa, &entry->devid, entry, PMT_XA_LIMIT, GFP_KERNEL);270 if (ret)271 return ret;272 273 dev = device_create(&intel_pmt_class, parent, MKDEV(0, 0), entry,274 "%s%d", ns->name, entry->devid);275 276 if (IS_ERR(dev)) {277 dev_err(parent, "Could not create %s%d device node\n",278 ns->name, entry->devid);279 ret = PTR_ERR(dev);280 goto fail_dev_create;281 }282 283 entry->kobj = &dev->kobj;284 285 if (ns->attr_grp) {286 ret = sysfs_create_group(entry->kobj, ns->attr_grp);287 if (ret)288 goto fail_sysfs_create_group;289 }290 291 /* if size is 0 assume no data buffer, so no file needed */292 if (!entry->size)293 return 0;294 295 res.start = entry->base_addr;296 res.end = res.start + entry->size - 1;297 res.flags = IORESOURCE_MEM;298 299 entry->base = devm_ioremap_resource(dev, &res);300 if (IS_ERR(entry->base)) {301 ret = PTR_ERR(entry->base);302 goto fail_ioremap;303 }304 305 sysfs_bin_attr_init(&entry->pmt_bin_attr);306 entry->pmt_bin_attr.attr.name = ns->name;307 entry->pmt_bin_attr.attr.mode = 0440;308 entry->pmt_bin_attr.mmap = intel_pmt_mmap;309 entry->pmt_bin_attr.read = intel_pmt_read;310 entry->pmt_bin_attr.size = entry->size;311 312 ret = sysfs_create_bin_file(&dev->kobj, &entry->pmt_bin_attr);313 if (ret)314 goto fail_ioremap;315 316 if (ns->pmt_add_endpoint) {317 ret = ns->pmt_add_endpoint(ivdev, entry);318 if (ret)319 goto fail_add_endpoint;320 }321 322 return 0;323 324fail_add_endpoint:325 sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr);326fail_ioremap:327 if (ns->attr_grp)328 sysfs_remove_group(entry->kobj, ns->attr_grp);329fail_sysfs_create_group:330 device_unregister(dev);331fail_dev_create:332 xa_erase(ns->xa, entry->devid);333 334 return ret;335}336 337int intel_pmt_dev_create(struct intel_pmt_entry *entry, struct intel_pmt_namespace *ns,338 struct intel_vsec_device *intel_vsec_dev, int idx)339{340 struct device *dev = &intel_vsec_dev->auxdev.dev;341 struct resource *disc_res;342 int ret;343 344 disc_res = &intel_vsec_dev->resource[idx];345 346 entry->disc_table = devm_ioremap_resource(dev, disc_res);347 if (IS_ERR(entry->disc_table))348 return PTR_ERR(entry->disc_table);349 350 ret = ns->pmt_header_decode(entry, dev);351 if (ret)352 return ret;353 354 ret = intel_pmt_populate_entry(entry, intel_vsec_dev, disc_res);355 if (ret)356 return ret;357 358 return intel_pmt_dev_register(entry, ns, dev);359}360EXPORT_SYMBOL_NS_GPL(intel_pmt_dev_create, INTEL_PMT);361 362void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,363 struct intel_pmt_namespace *ns)364{365 struct device *dev = kobj_to_dev(entry->kobj);366 367 if (entry->size)368 sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr);369 370 if (ns->attr_grp)371 sysfs_remove_group(entry->kobj, ns->attr_grp);372 373 device_unregister(dev);374 xa_erase(ns->xa, entry->devid);375}376EXPORT_SYMBOL_NS_GPL(intel_pmt_dev_destroy, INTEL_PMT);377 378static int __init pmt_class_init(void)379{380 return class_register(&intel_pmt_class);381}382 383static void __exit pmt_class_exit(void)384{385 class_unregister(&intel_pmt_class);386}387 388module_init(pmt_class_init);389module_exit(pmt_class_exit);390 391MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");392MODULE_DESCRIPTION("Intel PMT Class driver");393MODULE_LICENSE("GPL v2");394