1 // SPDX-License-Identifier: GPL-2.0
3 * Intel Platform Monitory Technology Telemetry driver
5 * Copyright (c) 2020, Intel Corporation.
8 * Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com>
11 #include <linux/kernel.h>
12 #include <linux/module.h>
14 #include <linux/pci.h>
16 #include "intel_pmt_class.h"
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)
26 intel_pmt_read(struct file
*filp
, struct kobject
*kobj
,
27 struct bin_attribute
*attr
, char *buf
, loff_t off
,
30 struct intel_pmt_entry
*entry
= container_of(attr
,
31 struct intel_pmt_entry
,
37 if (off
>= entry
->size
)
40 if (count
> entry
->size
- off
)
41 count
= entry
->size
- off
;
43 memcpy_fromio(buf
, entry
->base
+ off
, count
);
49 intel_pmt_mmap(struct file
*filp
, struct kobject
*kobj
,
50 struct bin_attribute
*attr
, struct vm_area_struct
*vma
)
52 struct intel_pmt_entry
*entry
= container_of(attr
,
53 struct intel_pmt_entry
,
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
);
61 if (vma
->vm_flags
& (VM_WRITE
| VM_MAYWRITE
))
64 psize
= (PFN_UP(entry
->base_addr
+ entry
->size
) - pfn
) * PAGE_SIZE
;
66 dev_err(dev
, "Requested mmap size is too large\n");
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
))
79 guid_show(struct device
*dev
, struct device_attribute
*attr
, char *buf
)
81 struct intel_pmt_entry
*entry
= dev_get_drvdata(dev
);
83 return sprintf(buf
, "0x%x\n", entry
->guid
);
85 static DEVICE_ATTR_RO(guid
);
87 static ssize_t
size_show(struct device
*dev
, struct device_attribute
*attr
,
90 struct intel_pmt_entry
*entry
= dev_get_drvdata(dev
);
92 return sprintf(buf
, "%zu\n", entry
->size
);
94 static DEVICE_ATTR_RO(size
);
97 offset_show(struct device
*dev
, struct device_attribute
*attr
, char *buf
)
99 struct intel_pmt_entry
*entry
= dev_get_drvdata(dev
);
101 return sprintf(buf
, "%lu\n", offset_in_page(entry
->base_addr
));
103 static DEVICE_ATTR_RO(offset
);
105 static struct attribute
*intel_pmt_attrs
[] = {
108 &dev_attr_offset
.attr
,
111 ATTRIBUTE_GROUPS(intel_pmt
);
113 static struct class intel_pmt_class
= {
115 .owner
= THIS_MODULE
,
116 .dev_groups
= intel_pmt_groups
,
119 static int intel_pmt_populate_entry(struct intel_pmt_entry
*entry
,
120 struct intel_pmt_header
*header
,
122 struct resource
*disc_res
)
124 struct pci_dev
*pci_dev
= to_pci_dev(dev
->parent
);
128 * The base offset should always be 8 byte aligned.
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.
134 bir
= GET_BIR(header
->base_offset
);
136 /* Local access and BARID only for now */
137 switch (header
->access_type
) {
141 "Unsupported BAR index %d for access type %d\n",
142 bir
, header
->access_type
);
146 * For access_type LOCAL, the base address is as follows:
147 * base address = end of discovery region + base offset
149 entry
->base_addr
= disc_res
->end
+ 1 + header
->base_offset
;
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.
157 entry
->base_addr
= pci_resource_start(pci_dev
, bir
) +
158 GET_ADDRESS(header
->base_offset
);
161 dev_err(dev
, "Unsupported access type %d\n",
162 header
->access_type
);
166 entry
->guid
= header
->guid
;
167 entry
->size
= header
->size
;
172 static int intel_pmt_dev_register(struct intel_pmt_entry
*entry
,
173 struct intel_pmt_namespace
*ns
,
174 struct device
*parent
)
180 ret
= xa_alloc(ns
->xa
, &entry
->devid
, entry
, PMT_XA_LIMIT
, GFP_KERNEL
);
184 dev
= device_create(&intel_pmt_class
, parent
, MKDEV(0, 0), entry
,
185 "%s%d", ns
->name
, entry
->devid
);
188 dev_err(parent
, "Could not create %s%d device node\n",
189 ns
->name
, entry
->devid
);
191 goto fail_dev_create
;
194 entry
->kobj
= &dev
->kobj
;
197 ret
= sysfs_create_group(entry
->kobj
, ns
->attr_grp
);
202 /* if size is 0 assume no data buffer, so no file needed */
206 res
.start
= entry
->base_addr
;
207 res
.end
= res
.start
+ entry
->size
- 1;
208 res
.flags
= IORESOURCE_MEM
;
210 entry
->base
= devm_ioremap_resource(dev
, &res
);
211 if (IS_ERR(entry
->base
)) {
212 ret
= PTR_ERR(entry
->base
);
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
;
223 ret
= sysfs_create_bin_file(&dev
->kobj
, &entry
->pmt_bin_attr
);
229 sysfs_remove_group(entry
->kobj
, ns
->attr_grp
);
231 device_unregister(dev
);
233 xa_erase(ns
->xa
, entry
->devid
);
238 int intel_pmt_dev_create(struct intel_pmt_entry
*entry
,
239 struct intel_pmt_namespace
*ns
,
240 struct platform_device
*pdev
, int idx
)
242 struct intel_pmt_header header
;
243 struct resource
*disc_res
;
246 disc_res
= platform_get_resource(pdev
, IORESOURCE_MEM
, idx
);
250 entry
->disc_table
= devm_platform_ioremap_resource(pdev
, idx
);
251 if (IS_ERR(entry
->disc_table
))
252 return PTR_ERR(entry
->disc_table
);
254 ret
= ns
->pmt_header_decode(entry
, &header
, &pdev
->dev
);
258 ret
= intel_pmt_populate_entry(entry
, &header
, &pdev
->dev
, disc_res
);
262 return intel_pmt_dev_register(entry
, ns
, &pdev
->dev
);
265 EXPORT_SYMBOL_GPL(intel_pmt_dev_create
);
267 void intel_pmt_dev_destroy(struct intel_pmt_entry
*entry
,
268 struct intel_pmt_namespace
*ns
)
270 struct device
*dev
= kobj_to_dev(entry
->kobj
);
273 sysfs_remove_bin_file(entry
->kobj
, &entry
->pmt_bin_attr
);
276 sysfs_remove_group(entry
->kobj
, ns
->attr_grp
);
278 device_unregister(dev
);
279 xa_erase(ns
->xa
, entry
->devid
);
281 EXPORT_SYMBOL_GPL(intel_pmt_dev_destroy
);
283 static int __init
pmt_class_init(void)
285 return class_register(&intel_pmt_class
);
288 static void __exit
pmt_class_exit(void)
290 class_unregister(&intel_pmt_class
);
293 module_init(pmt_class_init
);
294 module_exit(pmt_class_exit
);
296 MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
297 MODULE_DESCRIPTION("Intel PMT Class driver");
298 MODULE_LICENSE("GPL v2");