brintos

brintos / linux-shallow public Read only

0
0
Text · 9.7 KiB · c04bb7f Raw
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