File Index
Files and directories
Sign in
FILE

class.c

File preview with syntax highlighting.

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