| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Intel Platform Monitory Technology Telemetry driver
- *
- * Copyright (c) 2020, Intel Corporation.
- * All Rights Reserved.
- *
- * Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com>
- */
- #include <linux/kernel.h>
- #include <linux/log2.h>
- #include <linux/intel_vsec.h>
- #include <linux/io-64-nonatomic-lo-hi.h>
- #include <linux/module.h>
- #include <linux/mm.h>
- #include <linux/pci.h>
- #include <linux/sysfs.h>
- #include "class.h"
- #define PMT_XA_START 1
- #define PMT_XA_MAX INT_MAX
- #define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX)
- #define GUID_SPR_PUNIT 0x9956f43f
- bool intel_pmt_is_early_client_hw(struct device *dev)
- {
- struct intel_vsec_device *ivdev = dev_to_ivdev(dev);
- /*
- * Early implementations of PMT on client platforms have some
- * differences from the server platforms (which use the Out Of Band
- * Management Services Module OOBMSM).
- */
- return !!(ivdev->quirks & VSEC_QUIRK_EARLY_HW);
- }
- EXPORT_SYMBOL_NS_GPL(intel_pmt_is_early_client_hw, "INTEL_PMT");
- static inline int
- pmt_memcpy64_fromio(void *to, const u64 __iomem *from, size_t count)
- {
- int i, remain;
- u64 *buf = to;
- if (!IS_ALIGNED((unsigned long)from, 8))
- return -EFAULT;
- for (i = 0; i < count/8; i++)
- buf[i] = readq(&from[i]);
- /* Copy any remaining bytes */
- remain = count % 8;
- if (remain) {
- u64 tmp = readq(&from[i]);
- memcpy(&buf[i], &tmp, remain);
- }
- return count;
- }
- int pmt_telem_read_mmio(struct pci_dev *pdev, struct pmt_callbacks *cb, u32 guid, void *buf,
- void __iomem *addr, loff_t off, u32 count)
- {
- if (cb && cb->read_telem)
- return cb->read_telem(pdev, guid, buf, off, count);
- addr += off;
- if (guid == GUID_SPR_PUNIT)
- /* PUNIT on SPR only supports aligned 64-bit read */
- return pmt_memcpy64_fromio(buf, addr, count);
- memcpy_fromio(buf, addr, count);
- return count;
- }
- EXPORT_SYMBOL_NS_GPL(pmt_telem_read_mmio, "INTEL_PMT");
- /*
- * sysfs
- */
- static ssize_t
- intel_pmt_read(struct file *filp, struct kobject *kobj,
- const struct bin_attribute *attr, char *buf, loff_t off,
- size_t count)
- {
- struct intel_pmt_entry *entry = container_of(attr,
- struct intel_pmt_entry,
- pmt_bin_attr);
- if (off < 0)
- return -EINVAL;
- if (off >= entry->size)
- return 0;
- if (count > entry->size - off)
- count = entry->size - off;
- count = pmt_telem_read_mmio(entry->pcidev, entry->cb, entry->header.guid, buf,
- entry->base, off, count);
- return count;
- }
- static int
- intel_pmt_mmap(struct file *filp, struct kobject *kobj,
- const struct bin_attribute *attr, struct vm_area_struct *vma)
- {
- struct intel_pmt_entry *entry = container_of(attr,
- struct intel_pmt_entry,
- pmt_bin_attr);
- unsigned long vsize = vma->vm_end - vma->vm_start;
- struct device *dev = kobj_to_dev(kobj);
- unsigned long phys = entry->base_addr;
- unsigned long pfn = PFN_DOWN(phys);
- unsigned long psize;
- if (vma->vm_flags & (VM_WRITE | VM_MAYWRITE))
- return -EROFS;
- psize = (PFN_UP(entry->base_addr + entry->size) - pfn) * PAGE_SIZE;
- if (vsize > psize) {
- dev_err(dev, "Requested mmap size is too large\n");
- return -EINVAL;
- }
- vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
- if (io_remap_pfn_range(vma, vma->vm_start, pfn,
- vsize, vma->vm_page_prot))
- return -EAGAIN;
- return 0;
- }
- static ssize_t
- guid_show(struct device *dev, struct device_attribute *attr, char *buf)
- {
- struct intel_pmt_entry *entry = dev_get_drvdata(dev);
- return sysfs_emit(buf, "0x%x\n", entry->guid);
- }
- static DEVICE_ATTR_RO(guid);
- static ssize_t size_show(struct device *dev, struct device_attribute *attr,
- char *buf)
- {
- struct intel_pmt_entry *entry = dev_get_drvdata(dev);
- return sysfs_emit(buf, "%zu\n", entry->size);
- }
- static DEVICE_ATTR_RO(size);
- static ssize_t
- offset_show(struct device *dev, struct device_attribute *attr, char *buf)
- {
- struct intel_pmt_entry *entry = dev_get_drvdata(dev);
- return sysfs_emit(buf, "%lu\n", offset_in_page(entry->base_addr));
- }
- static DEVICE_ATTR_RO(offset);
- static struct attribute *intel_pmt_attrs[] = {
- &dev_attr_guid.attr,
- &dev_attr_size.attr,
- &dev_attr_offset.attr,
- NULL
- };
- static umode_t intel_pmt_attr_visible(struct kobject *kobj,
- struct attribute *attr, int n)
- {
- struct device *dev = container_of(kobj, struct device, kobj);
- struct auxiliary_device *auxdev = to_auxiliary_dev(dev->parent);
- struct intel_vsec_device *ivdev = auxdev_to_ivdev(auxdev);
- /*
- * Place the discovery features folder in /sys/class/intel_pmt, but
- * exclude the common attributes as they are not applicable.
- */
- if (ivdev->cap_id == ilog2(VSEC_CAP_DISCOVERY))
- return 0;
- return attr->mode;
- }
- static bool intel_pmt_group_visible(struct kobject *kobj)
- {
- return true;
- }
- DEFINE_SYSFS_GROUP_VISIBLE(intel_pmt);
- static const struct attribute_group intel_pmt_group = {
- .attrs = intel_pmt_attrs,
- .is_visible = SYSFS_GROUP_VISIBLE(intel_pmt),
- };
- __ATTRIBUTE_GROUPS(intel_pmt);
- struct class intel_pmt_class = {
- .name = "intel_pmt",
- .dev_groups = intel_pmt_groups,
- };
- 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)
- {
- struct pci_dev *pci_dev = ivdev->pcidev;
- struct device *dev = &ivdev->auxdev.dev;
- struct intel_pmt_header *header = &entry->header;
- u8 bir;
- /*
- * 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);
- /* Local access and BARID only for now */
- switch (header->access_type) {
- case ACCESS_LOCAL:
- if (bir) {
- dev_err(dev,
- "Unsupported BAR index %d for access type %d\n",
- bir, header->access_type);
- return -EINVAL;
- }
- /*
- * For access_type LOCAL, the base address is as follows:
- * base address = end of discovery region + base offset
- */
- entry->base_addr = disc_res->end + 1 + header->base_offset;
- /*
- * Some hardware use a different calculation for the base address
- * when access_type == ACCESS_LOCAL. On the these systems
- * ACCESS_LOCAL refers to an address in the same BAR as the
- * header but at a fixed offset. But as the header address was
- * supplied to the driver, we don't know which BAR it was in.
- * So search for the bar whose range includes the header address.
- */
- if (intel_pmt_is_early_client_hw(dev)) {
- int i;
- entry->base_addr = 0;
- for (i = 0; i < 6; i++)
- if (disc_res->start >= pci_resource_start(pci_dev, i) &&
- (disc_res->start <= pci_resource_end(pci_dev, i))) {
- entry->base_addr = pci_resource_start(pci_dev, i) +
- header->base_offset;
- break;
- }
- if (!entry->base_addr)
- return -EINVAL;
- }
- break;
- case ACCESS_BARID:
- /* Use the provided base address if it exists */
- if (ivdev->base_addr) {
- entry->base_addr = ivdev->base_addr +
- GET_ADDRESS(header->base_offset);
- break;
- }
- /*
- * If another BAR was specified then the base offset
- * represents the offset within that BAR. SO retrieve the
- * address from the parent PCI device and add offset.
- */
- entry->base_addr = pci_resource_start(pci_dev, bir) +
- GET_ADDRESS(header->base_offset);
- break;
- default:
- dev_err(dev, "Unsupported access type %d\n",
- header->access_type);
- return -EINVAL;
- }
- entry->pcidev = pci_dev;
- entry->guid = header->guid;
- entry->size = header->size;
- entry->cb = ivdev->priv_data;
- return 0;
- }
- static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
- struct intel_pmt_namespace *ns,
- struct device *parent)
- {
- struct intel_vsec_device *ivdev = dev_to_ivdev(parent);
- struct resource res = {0};
- struct device *dev;
- int ret;
- ret = xa_alloc(ns->xa, &entry->devid, entry, PMT_XA_LIMIT, GFP_KERNEL);
- if (ret)
- return ret;
- dev = device_create(&intel_pmt_class, parent, MKDEV(0, 0), entry,
- "%s%d", ns->name, entry->devid);
- if (IS_ERR(dev)) {
- dev_err(parent, "Could not create %s%d device node\n",
- ns->name, entry->devid);
- ret = PTR_ERR(dev);
- goto fail_dev_create;
- }
- entry->kobj = &dev->kobj;
- if (entry->attr_grp) {
- ret = sysfs_create_group(entry->kobj, entry->attr_grp);
- if (ret)
- goto fail_sysfs_create_group;
- }
- /* if size is 0 assume no data buffer, so no file needed */
- if (!entry->size)
- return 0;
- res.start = entry->base_addr;
- res.end = res.start + entry->size - 1;
- res.flags = IORESOURCE_MEM;
- entry->base = devm_ioremap_resource(dev, &res);
- if (IS_ERR(entry->base)) {
- ret = PTR_ERR(entry->base);
- goto fail_ioremap;
- }
- sysfs_bin_attr_init(&entry->pmt_bin_attr);
- entry->pmt_bin_attr.attr.name = ns->name;
- entry->pmt_bin_attr.attr.mode = 0440;
- entry->pmt_bin_attr.mmap = intel_pmt_mmap;
- entry->pmt_bin_attr.read = intel_pmt_read;
- entry->pmt_bin_attr.size = entry->size;
- ret = sysfs_create_bin_file(&dev->kobj, &entry->pmt_bin_attr);
- if (ret)
- goto fail_ioremap;
- if (ns->pmt_add_endpoint) {
- ret = ns->pmt_add_endpoint(ivdev, entry);
- if (ret)
- goto fail_add_endpoint;
- }
- return 0;
- fail_add_endpoint:
- sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr);
- fail_ioremap:
- if (entry->attr_grp)
- sysfs_remove_group(entry->kobj, entry->attr_grp);
- fail_sysfs_create_group:
- device_unregister(dev);
- fail_dev_create:
- xa_erase(ns->xa, entry->devid);
- return ret;
- }
- 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);
- ret = ns->pmt_header_decode(entry, dev);
- if (ret)
- return ret;
- ret = intel_pmt_populate_entry(entry, intel_vsec_dev, disc_res);
- if (ret)
- return ret;
- return intel_pmt_dev_register(entry, ns, dev);
- }
- EXPORT_SYMBOL_NS_GPL(intel_pmt_dev_create, "INTEL_PMT");
- void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
- struct intel_pmt_namespace *ns)
- {
- struct device *dev = kobj_to_dev(entry->kobj);
- if (entry->size)
- sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr);
- if (entry->attr_grp)
- sysfs_remove_group(entry->kobj, entry->attr_grp);
- device_unregister(dev);
- xa_erase(ns->xa, entry->devid);
- }
- EXPORT_SYMBOL_NS_GPL(intel_pmt_dev_destroy, "INTEL_PMT");
- static int __init pmt_class_init(void)
- {
- return class_register(&intel_pmt_class);
- }
- static void __exit pmt_class_exit(void)
- {
- class_unregister(&intel_pmt_class);
- }
- module_init(pmt_class_init);
- module_exit(pmt_class_exit);
- MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
- MODULE_DESCRIPTION("Intel PMT Class driver");
- MODULE_LICENSE("GPL v2");
|