File Index
Files and directories
Sign in
FILE

discovery.c

File preview with syntax highlighting.

linux-6.19.14/drivers/platform/x86/intel/pmt/discovery.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Intel Platform Monitory Technology Discovery driver
  4 *
  5 * Copyright (c) 2025, Intel Corporation.
  6 * All Rights Reserved.
  7 */
  8
  9#include <linux/auxiliary_bus.h>
 10#include <linux/bitfield.h>
 11#include <linux/bits.h>
 12#include <linux/bug.h>
 13#include <linux/cleanup.h>
 14#include <linux/container_of.h>
 15#include <linux/device.h>
 16#include <linux/err.h>
 17#include <linux/io.h>
 18#include <linux/ioport.h>
 19#include <linux/kdev_t.h>
 20#include <linux/kobject.h>
 21#include <linux/list.h>
 22#include <linux/module.h>
 23#include <linux/mutex.h>
 24#include <linux/overflow.h>
 25#include <linux/pci.h>
 26#include <linux/slab.h>
 27#include <linux/string_choices.h>
 28#include <linux/sysfs.h>
 29#include <linux/types.h>
 30#include <linux/uaccess.h>
 31
 32#include <linux/intel_pmt_features.h>
 33#include <linux/intel_vsec.h>
 34
 35#include "class.h"
 36
 37#define MAX_FEATURE_VERSION	0
 38#define DT_TBIR			GENMASK(2, 0)
 39#define FEAT_ATTR_SIZE(x)	((x) * sizeof(u32))
 40#define PMT_GUID_SIZE(x)	((x) * sizeof(u32))
 41#define PMT_ACCESS_TYPE_RSVD	0xF
 42#define SKIP_FEATURE		1
 43
 44struct feature_discovery_table {
 45	u32	access_type:4;
 46	u32	version:8;
 47	u32	size:16;
 48	u32	reserved:4;
 49	u32	id;
 50	u32	offset;
 51	u32	reserved2;
 52};
 53
 54/* Common feature table header */
 55struct feature_header {
 56	u32	attr_size:8;
 57	u32	num_guids:8;
 58	u32	reserved:16;
 59};
 60
 61/* Feature attribute fields */
 62struct caps {
 63	u32		caps;
 64};
 65
 66struct command {
 67	u32		max_stream_size:16;
 68	u32		max_command_size:16;
 69};
 70
 71struct watcher {
 72	u32		reserved:21;
 73	u32		period:11;
 74	struct command	command;
 75};
 76
 77struct rmid {
 78	u32		num_rmids:16;	/* Number of Resource Monitoring IDs */
 79	u32		reserved:16;
 80	struct watcher	watcher;
 81};
 82
 83struct feature_table {
 84	struct feature_header	header;
 85	struct caps		caps;
 86	union {
 87		struct command command;
 88		struct watcher watcher;
 89		struct rmid rmid;
 90	};
 91	u32			*guids;
 92};
 93
 94/* For backreference in struct feature */
 95struct pmt_features_priv;
 96
 97struct feature {
 98	struct feature_table		table;
 99	struct kobject			kobj;
100	struct pmt_features_priv	*priv;
101	struct list_head		list;
102	const struct attribute_group	*attr_group;
103	enum pmt_feature_id		id;
104};
105
106struct pmt_features_priv {
107	struct device		*parent;
108	struct device		*dev;
109	int			count;
110	u32			mask;
111	struct feature		feature[];
112};
113
114static LIST_HEAD(pmt_feature_list);
115static DEFINE_MUTEX(feature_list_lock);
116
117#define to_pmt_feature(x) container_of(x, struct feature, kobj)
118static void pmt_feature_release(struct kobject *kobj)
119{
120}
121
122static ssize_t caps_show(struct kobject *kobj, struct kobj_attribute *attr,
123			 char *buf)
124{
125	struct feature *feature = to_pmt_feature(kobj);
126	struct pmt_cap **pmt_caps;
127	u32 caps = feature->table.caps.caps;
128	ssize_t ret = 0;
129
130	switch (feature->id) {
131	case FEATURE_PER_CORE_PERF_TELEM:
132		pmt_caps = pmt_caps_pcpt;
133		break;
134	case FEATURE_PER_CORE_ENV_TELEM:
135		pmt_caps = pmt_caps_pcet;
136		break;
137	case FEATURE_PER_RMID_PERF_TELEM:
138		pmt_caps = pmt_caps_rmid_perf;
139		break;
140	case FEATURE_ACCEL_TELEM:
141		pmt_caps = pmt_caps_accel;
142		break;
143	case FEATURE_UNCORE_TELEM:
144		pmt_caps = pmt_caps_uncore;
145		break;
146	case FEATURE_CRASH_LOG:
147		pmt_caps = pmt_caps_crashlog;
148		break;
149	case FEATURE_PETE_LOG:
150		pmt_caps = pmt_caps_pete;
151		break;
152	case FEATURE_TPMI_CTRL:
153		pmt_caps = pmt_caps_tpmi;
154		break;
155	case FEATURE_TRACING:
156		pmt_caps = pmt_caps_tracing;
157		break;
158	case FEATURE_PER_RMID_ENERGY_TELEM:
159		pmt_caps = pmt_caps_rmid_energy;
160		break;
161	default:
162		return -EINVAL;
163	}
164
165	while (*pmt_caps) {
166		struct pmt_cap *pmt_cap = *pmt_caps;
167
168		while (pmt_cap->name) {
169			ret += sysfs_emit_at(buf, ret, "%-40s Available: %s\n", pmt_cap->name,
170					     str_yes_no(pmt_cap->mask & caps));
171			pmt_cap++;
172		}
173		pmt_caps++;
174	}
175
176	return ret;
177}
178static struct kobj_attribute caps_attribute = __ATTR_RO(caps);
179
180static struct watcher *get_watcher(struct feature *feature)
181{
182	switch (feature_layout[feature->id]) {
183	case LAYOUT_RMID:
184		return &feature->table.rmid.watcher;
185	case LAYOUT_WATCHER:
186		return &feature->table.watcher;
187	default:
188		return ERR_PTR(-EINVAL);
189	}
190}
191
192static struct command *get_command(struct feature *feature)
193{
194	switch (feature_layout[feature->id]) {
195	case LAYOUT_RMID:
196		return &feature->table.rmid.watcher.command;
197	case LAYOUT_WATCHER:
198		return &feature->table.watcher.command;
199	case LAYOUT_COMMAND:
200		return &feature->table.command;
201	default:
202		return ERR_PTR(-EINVAL);
203	}
204}
205
206static ssize_t num_rmids_show(struct kobject *kobj,
207			      struct kobj_attribute *attr, char *buf)
208{
209	struct feature *feature = to_pmt_feature(kobj);
210
211	return sysfs_emit(buf, "%u\n", feature->table.rmid.num_rmids);
212}
213static struct kobj_attribute num_rmids_attribute = __ATTR_RO(num_rmids);
214
215static ssize_t min_watcher_period_ms_show(struct kobject *kobj,
216					  struct kobj_attribute *attr, char *buf)
217{
218	struct feature *feature = to_pmt_feature(kobj);
219	struct watcher *watcher = get_watcher(feature);
220
221	if (IS_ERR(watcher))
222		return PTR_ERR(watcher);
223
224	return sysfs_emit(buf, "%u\n", watcher->period);
225}
226static struct kobj_attribute min_watcher_period_ms_attribute =
227	__ATTR_RO(min_watcher_period_ms);
228
229static ssize_t max_stream_size_show(struct kobject *kobj,
230				    struct kobj_attribute *attr, char *buf)
231{
232	struct feature *feature = to_pmt_feature(kobj);
233	struct command *command = get_command(feature);
234
235	if (IS_ERR(command))
236		return PTR_ERR(command);
237
238	return sysfs_emit(buf, "%u\n", command->max_stream_size);
239}
240static struct kobj_attribute max_stream_size_attribute =
241	__ATTR_RO(max_stream_size);
242
243static ssize_t max_command_size_show(struct kobject *kobj,
244				     struct kobj_attribute *attr, char *buf)
245{
246	struct feature *feature = to_pmt_feature(kobj);
247	struct command *command = get_command(feature);
248
249	if (IS_ERR(command))
250		return PTR_ERR(command);
251
252	return sysfs_emit(buf, "%u\n", command->max_command_size);
253}
254static struct kobj_attribute max_command_size_attribute =
255	__ATTR_RO(max_command_size);
256
257static ssize_t guids_show(struct kobject *kobj, struct kobj_attribute *attr,
258			  char *buf)
259{
260	struct feature *feature = to_pmt_feature(kobj);
261	int i, count = 0;
262
263	for (i = 0; i < feature->table.header.num_guids; i++)
264		count += sysfs_emit_at(buf, count, "0x%x\n",
265				       feature->table.guids[i]);
266
267	return count;
268}
269static struct kobj_attribute guids_attribute = __ATTR_RO(guids);
270
271static struct attribute *pmt_feature_rmid_attrs[] = {
272	&caps_attribute.attr,
273	&num_rmids_attribute.attr,
274	&min_watcher_period_ms_attribute.attr,
275	&max_stream_size_attribute.attr,
276	&max_command_size_attribute.attr,
277	&guids_attribute.attr,
278	NULL
279};
280ATTRIBUTE_GROUPS(pmt_feature_rmid);
281
282static const struct kobj_type pmt_feature_rmid_ktype = {
283	.sysfs_ops = &kobj_sysfs_ops,
284	.release = pmt_feature_release,
285	.default_groups = pmt_feature_rmid_groups,
286};
287
288static struct attribute *pmt_feature_watcher_attrs[] = {
289	&caps_attribute.attr,
290	&min_watcher_period_ms_attribute.attr,
291	&max_stream_size_attribute.attr,
292	&max_command_size_attribute.attr,
293	&guids_attribute.attr,
294	NULL
295};
296ATTRIBUTE_GROUPS(pmt_feature_watcher);
297
298static const struct kobj_type pmt_feature_watcher_ktype = {
299	.sysfs_ops = &kobj_sysfs_ops,
300	.release = pmt_feature_release,
301	.default_groups = pmt_feature_watcher_groups,
302};
303
304static struct attribute *pmt_feature_command_attrs[] = {
305	&caps_attribute.attr,
306	&max_stream_size_attribute.attr,
307	&max_command_size_attribute.attr,
308	&guids_attribute.attr,
309	NULL
310};
311ATTRIBUTE_GROUPS(pmt_feature_command);
312
313static const struct kobj_type pmt_feature_command_ktype = {
314	.sysfs_ops = &kobj_sysfs_ops,
315	.release = pmt_feature_release,
316	.default_groups = pmt_feature_command_groups,
317};
318
319static struct attribute *pmt_feature_guids_attrs[] = {
320	&caps_attribute.attr,
321	&guids_attribute.attr,
322	NULL
323};
324ATTRIBUTE_GROUPS(pmt_feature_guids);
325
326static const struct kobj_type pmt_feature_guids_ktype = {
327	.sysfs_ops = &kobj_sysfs_ops,
328	.release = pmt_feature_release,
329	.default_groups = pmt_feature_guids_groups,
330};
331
332static int
333pmt_feature_get_disc_table(struct pmt_features_priv *priv,
334			   struct resource *disc_res,
335			   struct feature_discovery_table *disc_tbl)
336{
337	void __iomem *disc_base;
338
339	disc_base = devm_ioremap_resource(priv->dev, disc_res);
340	if (IS_ERR(disc_base))
341		return PTR_ERR(disc_base);
342
343	memcpy_fromio(disc_tbl, disc_base, sizeof(*disc_tbl));
344
345	devm_iounmap(priv->dev, disc_base);
346
347	if (priv->mask & BIT(disc_tbl->id))
348		return dev_err_probe(priv->dev, -EINVAL, "Duplicate feature: %s\n",
349				     pmt_feature_names[disc_tbl->id]);
350
351	/*
352	 * Some devices may expose non-functioning entries that are
353	 * reserved for future use. They have zero size. Do not fail
354	 * probe for these. Just ignore them.
355	 */
356	if (disc_tbl->size == 0 || disc_tbl->access_type == PMT_ACCESS_TYPE_RSVD)
357		return SKIP_FEATURE;
358
359	if (disc_tbl->version > MAX_FEATURE_VERSION)
360		return SKIP_FEATURE;
361
362	if (!pmt_feature_id_is_valid(disc_tbl->id))
363		return SKIP_FEATURE;
364
365	priv->mask |= BIT(disc_tbl->id);
366
367	return 0;
368}
369
370static int
371pmt_feature_get_feature_table(struct pmt_features_priv *priv,
372			      struct feature *feature,
373			      struct feature_discovery_table *disc_tbl,
374			      struct resource *disc_res)
375{
376	struct feature_table *feat_tbl = &feature->table;
377	struct feature_header *header;
378	struct resource res = {};
379	resource_size_t res_size;
380	void __iomem *feat_base, *feat_offset;
381	void *tbl_offset;
382	size_t size;
383	u32 *guids;
384	u8 tbir;
385
386	tbir = FIELD_GET(DT_TBIR, disc_tbl->offset);
387
388	switch (disc_tbl->access_type) {
389	case ACCESS_LOCAL:
390		if (tbir)
391			return dev_err_probe(priv->dev, -EINVAL,
392				"Unsupported BAR index %u for access type %u\n",
393				tbir, disc_tbl->access_type);
394
395
396		/*
397		 * For access_type LOCAL, the base address is as follows:
398		 * base address = end of discovery region + base offset + 1
399		 */
400		res = DEFINE_RES_MEM(disc_res->end + disc_tbl->offset + 1,
401				     disc_tbl->size * sizeof(u32));
402		break;
403
404	default:
405		return dev_err_probe(priv->dev, -EINVAL, "Unrecognized access_type %u\n",
406				     disc_tbl->access_type);
407	}
408
409	feature->id = disc_tbl->id;
410
411	/* Get the feature table */
412	feat_base = devm_ioremap_resource(priv->dev, &res);
413	if (IS_ERR(feat_base))
414		return PTR_ERR(feat_base);
415
416	feat_offset = feat_base;
417	tbl_offset = feat_tbl;
418
419	/* Get the header */
420	header = &feat_tbl->header;
421	memcpy_fromio(header, feat_offset, sizeof(*header));
422
423	/* Validate fields fit within mapped resource */
424	size = sizeof(*header) + FEAT_ATTR_SIZE(header->attr_size) +
425	       PMT_GUID_SIZE(header->num_guids);
426	res_size = resource_size(&res);
427	if (WARN(size > res_size, "Bad table size %zu > %pa", size, &res_size))
428		return -EINVAL;
429
430	/* Get the feature attributes, including capability fields */
431	tbl_offset += sizeof(*header);
432	feat_offset += sizeof(*header);
433
434	memcpy_fromio(tbl_offset, feat_offset, FEAT_ATTR_SIZE(header->attr_size));
435
436	/* Finally, get the guids */
437	guids = devm_kmalloc(priv->dev, PMT_GUID_SIZE(header->num_guids), GFP_KERNEL);
438	if (!guids)
439		return -ENOMEM;
440
441	feat_offset += FEAT_ATTR_SIZE(header->attr_size);
442
443	memcpy_fromio(guids, feat_offset, PMT_GUID_SIZE(header->num_guids));
444
445	feat_tbl->guids = guids;
446
447	devm_iounmap(priv->dev, feat_base);
448
449	return 0;
450}
451
452static void pmt_features_add_feat(struct feature *feature)
453{
454	guard(mutex)(&feature_list_lock);
455	list_add(&feature->list, &pmt_feature_list);
456}
457
458static void pmt_features_remove_feat(struct feature *feature)
459{
460	guard(mutex)(&feature_list_lock);
461	list_del(&feature->list);
462}
463
464/* Get the discovery table and use it to get the feature table */
465static int pmt_features_discovery(struct pmt_features_priv *priv,
466				  struct feature *feature,
467				  struct intel_vsec_device *ivdev,
468				  int idx)
469{
470	struct feature_discovery_table disc_tbl = {}; /* Avoid false warning */
471	struct resource *disc_res = &ivdev->resource[idx];
472	const struct kobj_type *ktype;
473	int ret;
474
475	ret = pmt_feature_get_disc_table(priv, disc_res, &disc_tbl);
476	if (ret)
477		return ret;
478
479	ret = pmt_feature_get_feature_table(priv, feature, &disc_tbl, disc_res);
480	if (ret)
481		return ret;
482
483	switch (feature_layout[feature->id]) {
484	case LAYOUT_RMID:
485		ktype = &pmt_feature_rmid_ktype;
486		feature->attr_group = &pmt_feature_rmid_group;
487		break;
488	case LAYOUT_WATCHER:
489		ktype = &pmt_feature_watcher_ktype;
490		feature->attr_group = &pmt_feature_watcher_group;
491		break;
492	case LAYOUT_COMMAND:
493		ktype = &pmt_feature_command_ktype;
494		feature->attr_group = &pmt_feature_command_group;
495		break;
496	case LAYOUT_CAPS_ONLY:
497		ktype = &pmt_feature_guids_ktype;
498		feature->attr_group = &pmt_feature_guids_group;
499		break;
500	default:
501		return -EINVAL;
502	}
503
504	ret = kobject_init_and_add(&feature->kobj, ktype, &priv->dev->kobj,
505				   "%s", pmt_feature_names[feature->id]);
506	if (ret) {
507		kobject_put(&feature->kobj);
508		return ret;
509	}
510
511	kobject_uevent(&feature->kobj, KOBJ_ADD);
512	pmt_features_add_feat(feature);
513
514	return 0;
515}
516
517static void pmt_features_remove(struct auxiliary_device *auxdev)
518{
519	struct pmt_features_priv *priv = auxiliary_get_drvdata(auxdev);
520	int i;
521
522	for (i = 0; i < priv->count; i++) {
523		struct feature *feature = &priv->feature[i];
524
525		pmt_features_remove_feat(feature);
526		sysfs_remove_group(&feature->kobj, feature->attr_group);
527		kobject_put(&feature->kobj);
528	}
529
530	device_unregister(priv->dev);
531}
532
533static int pmt_features_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
534{
535	struct intel_vsec_device *ivdev = auxdev_to_ivdev(auxdev);
536	struct pmt_features_priv *priv;
537	size_t size;
538	int ret, i;
539
540	size = struct_size(priv, feature, ivdev->num_resources);
541	priv = devm_kzalloc(&auxdev->dev, size, GFP_KERNEL);
542	if (!priv)
543		return -ENOMEM;
544
545	priv->parent = &ivdev->pcidev->dev;
546	auxiliary_set_drvdata(auxdev, priv);
547
548	priv->dev = device_create(&intel_pmt_class, &auxdev->dev, MKDEV(0, 0), priv,
549				  "%s-%s", "features", dev_name(priv->parent));
550	if (IS_ERR(priv->dev))
551		return dev_err_probe(&auxdev->dev, PTR_ERR(priv->dev),
552				     "Could not create %s-%s device node\n",
553				     "features", dev_name(priv->parent));
554
555	/* Initialize each feature */
556	for (i = 0; i < ivdev->num_resources; i++) {
557		struct feature *feature = &priv->feature[priv->count];
558
559		ret = pmt_features_discovery(priv, feature, ivdev, i);
560		if (ret == SKIP_FEATURE)
561			continue;
562		if (ret != 0)
563			goto abort_probe;
564
565		feature->priv = priv;
566		priv->count++;
567	}
568
569	return 0;
570
571abort_probe:
572	/*
573	 * Only fully initialized features are tracked in priv->count, which is
574	 * incremented only after a feature is completely set up (i.e., after
575	 * discovery and sysfs registration). If feature initialization fails,
576	 * the failing feature's state is local and does not require rollback.
577	 *
578	 * Therefore, on error, we can safely call the driver's remove() routine
579	 * pmt_features_remove() to clean up only those features that were
580	 * fully initialized and counted. All other resources are device-managed
581	 * and will be cleaned up automatically during device_unregister().
582	 */
583	pmt_features_remove(auxdev);
584
585	return ret;
586}
587
588static void pmt_get_features(struct intel_pmt_entry *entry, struct feature *f)
589{
590	int num_guids = f->table.header.num_guids;
591	int i;
592
593	for (i = 0; i < num_guids; i++) {
594		if (f->table.guids[i] != entry->guid)
595			continue;
596
597		entry->feature_flags |= BIT(f->id);
598
599		if (feature_layout[f->id] == LAYOUT_RMID)
600			entry->num_rmids = f->table.rmid.num_rmids;
601		else
602			entry->num_rmids = 0; /* entry is kzalloc but set anyway */
603	}
604}
605
606void intel_pmt_get_features(struct intel_pmt_entry *entry)
607{
608	struct feature *feature;
609
610	mutex_lock(&feature_list_lock);
611	list_for_each_entry(feature, &pmt_feature_list, list) {
612		if (feature->priv->parent != &entry->ep->pcidev->dev)
613			continue;
614
615		pmt_get_features(entry, feature);
616	}
617	mutex_unlock(&feature_list_lock);
618}
619EXPORT_SYMBOL_NS_GPL(intel_pmt_get_features, "INTEL_PMT");
620
621static const struct auxiliary_device_id pmt_features_id_table[] = {
622	{ .name = "intel_vsec.discovery" },
623	{}
624};
625MODULE_DEVICE_TABLE(auxiliary, pmt_features_id_table);
626
627static struct auxiliary_driver pmt_features_aux_driver = {
628	.id_table	= pmt_features_id_table,
629	.remove		= pmt_features_remove,
630	.probe		= pmt_features_probe,
631};
632module_auxiliary_driver(pmt_features_aux_driver);
633
634MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
635MODULE_DESCRIPTION("Intel PMT Discovery driver");
636MODULE_LICENSE("GPL");
637MODULE_IMPORT_NS("INTEL_PMT");
~/ Read-only file preview.