X86 platform drivers
 help / color / mirror / Atom feed
From: "David E. Box" <david.e.box@linux.intel.com>
To: linux-kernel@vger.kernel.org, david.e.box@linux.intel.com,
	ilpo.jarvinen@linux.intel.com, andriy.shevchenko@linux.intel.com,
	platform-driver-x86@vger.kernel.org
Subject: [PATCH 13/17] tools/arch/x86/pmtctl: Add pmtctl 'list' command
Date: Mon, 25 May 2026 18:47:11 -0700	[thread overview]
Message-ID: <20260526014719.2248380-14-david.e.box@linux.intel.com> (raw)
In-Reply-To: <20260526014719.2248380-1-david.e.box@linux.intel.com>

Register the 'list' subcommand in the CLI dispatcher and add its
implementation in src/cmd_list.c. The command enumerates PMT devices
and metrics, with optional filtering by GUID or endpoint name.

Per-command --help, JSON output, and tabular formatting are handled
locally inside cmd_list.

Assisted-by: GitHub-Copilot:claude-opus-4.7
Signed-off-by: David E. Box <david.e.box@linux.intel.com>
---
 tools/arch/x86/pmtctl/Makefile             |   1 +
 tools/arch/x86/pmtctl/include/pmtctl_cli.h |   2 +
 tools/arch/x86/pmtctl/src/cmd_list.c       | 786 +++++++++++++++++++++
 tools/arch/x86/pmtctl/src/main.c           |  13 +
 4 files changed, 802 insertions(+)
 create mode 100644 tools/arch/x86/pmtctl/src/cmd_list.c

diff --git a/tools/arch/x86/pmtctl/Makefile b/tools/arch/x86/pmtctl/Makefile
index 83bca8c312e7..ee6633a6f435 100644
--- a/tools/arch/x86/pmtctl/Makefile
+++ b/tools/arch/x86/pmtctl/Makefile
@@ -41,6 +41,7 @@ SAMPLE_TARGET := $(BUILDDIR)/samples/libpmtctl_sample
 
 SRC := \
 	$(SRCDIR)/main.c \
+	$(SRCDIR)/cmd_list.c \
 	$(SRCDIR)/pager.c
 
 OBJ := $(patsubst $(SRCDIR)/%.c,$(BUILDDIR)/%.o,$(SRC))
diff --git a/tools/arch/x86/pmtctl/include/pmtctl_cli.h b/tools/arch/x86/pmtctl/include/pmtctl_cli.h
index 0b99dfe0ed64..eb5efb0b650f 100644
--- a/tools/arch/x86/pmtctl/include/pmtctl_cli.h
+++ b/tools/arch/x86/pmtctl/include/pmtctl_cli.h
@@ -9,4 +9,6 @@
 FILE *pmtctl_start_pager(const struct pmt_global_opts *gopts);
 void pmtctl_finish_pager(FILE *out);
 
+int cmd_list(int argc, char **argv, const struct pmt_global_opts *gopts);
+
 #endif
diff --git a/tools/arch/x86/pmtctl/src/cmd_list.c b/tools/arch/x86/pmtctl/src/cmd_list.c
new file mode 100644
index 000000000000..a4164bf75dfb
--- /dev/null
+++ b/tools/arch/x86/pmtctl/src/cmd_list.c
@@ -0,0 +1,786 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#define LIST_BASE_INDENT    2
+#define LIST_CONT_INDENT    6
+#define LIST_DEFAULT_COLS  80
+
+/* Column widths for the --guids table */
+#define GCOL_GUID   8
+#define GCOL_COUNT  5
+#define GCOL_DEF   10
+#define GCOL_PLAT   8
+
+#define LOG_PREFIX "cmd_list"
+#include "lib/log.h"
+
+#include "lib/common.h"
+#include "lib/device.h"
+#include "lib/metrics_db.h"
+#include "lib/pmt_guid.h"
+#include "lib/pmtctl_context.h"
+
+#include "pmtctl_cli.h"
+
+struct list_opts {
+	bool show_help;
+	bool devices_only;
+	bool guids_only;
+	bool keep_screen;
+	const char *device_selector;   /* raw "guid=...,ep=..." string (command-local fallback) */
+};
+
+struct list_metric_view {
+	const char *device_name;       /* GUID: "22806802" */
+	const char *platform_group;    /* platform group name */
+	const char *group_name;        /* pmt_guid->name */
+	const char *group_description; /* pmt_guid->description (may be NULL) */
+	const char *name;              /* metric name */
+	const char *desc;              /* metric description (may be NULL) */
+};
+
+struct guid_defs {
+	const char *platform_group;
+	const char *name;
+	const struct pmt_guid *pg;  /* registry entry (may be NULL) */
+	uint32_t guid;
+	bool from_builtin;
+	bool from_json;
+};
+
+struct guid_sys {
+	uint32_t guid;
+	int endpoints;
+};
+
+static int guid_defs_find(struct guid_defs *arr, int n, uint32_t guid)
+{
+	for (int i = 0; i < n; i++) {
+		if (arr[i].guid == guid)
+			return i;
+	}
+
+	return -1;
+}
+
+static int guid_defs_cmp_platform_group(const void *a, const void *b)
+{
+	const struct guid_defs *ga = (const struct guid_defs *)a;
+	const struct guid_defs *gb = (const struct guid_defs *)b;
+	const char *pg_a = ga->platform_group;
+	const char *pg_b = gb->platform_group;
+
+	if (pg_a && pg_b) {
+		int cmp = strcmp(pg_a, pg_b);
+
+		if (cmp)
+			return cmp;
+	} else if (pg_a) {
+		return -1;
+	} else if (pg_b) {
+		return 1;
+	}
+
+	if (ga->guid < gb->guid)
+		return -1;
+	if (ga->guid > gb->guid)
+		return 1;
+
+	return 0;
+}
+
+static int guid_sys_find(struct guid_sys *arr, int n, uint32_t guid)
+{
+	for (int i = 0; i < n; i++) {
+		if (arr[i].guid == guid)
+			return i;
+	}
+
+	return -1;
+}
+
+static bool is_blank_string(const char *s)
+{
+	while (*s) {
+		if (!isspace((unsigned char)*s))
+			return false;
+		s++;
+	}
+
+	return true;
+}
+
+static int do_list_guids(const int *dev_indices, int num_sel_devices)
+{
+	const struct pmtctl_context *ctx = pmtctl_get_ctx();
+	auto_free struct guid_defs *defs = NULL;
+	auto_free struct guid_sys  *sys  = NULL;
+	int num_defs = 0;
+	int n_sys = 0;
+
+	/* ----- 1) collect GUIDs from metric definitions ----- */
+	defs = calloc(ctx->metrics.total, sizeof(*defs));
+	if (!defs)
+		return log_ret(-ENOMEM, "could not build guid list");
+
+	for (int i = 0; i < ctx->metrics.total; i++) {
+		const struct pmt_metric_def *md;
+		const struct pmt_metrics_block *block;
+		uint32_t guid;
+		int idx;
+
+		md = pmt_metrics_at(&ctx->metrics, i);
+		if (!md)
+			continue;
+
+		block = pmt_metrics_block_for(&ctx->metrics, i);
+		if (!block)
+			continue;
+
+		/*
+		 * Fold every metric def into a single per-GUID row in defs[].
+		 * Many defs share the same GUID, so look up an existing row
+		 * and append a new one only the first time we see this GUID.
+		 * The metric-level fields below (from_builtin/from_json,
+		 * platform_group, name) then accumulate onto that row.
+		 *
+		 * A NULL md->guid means a provider failed to intern (or parse)
+		 * the GUID for this def; skip it rather than fold every such
+		 * def into a bogus "00000000" bucket.
+		 */
+		if (!md->guid) {
+			log_warn("metric def %d (%s) has no GUID; skipping",
+				 i, md->event_name ? md->event_name : "?");
+			continue;
+		}
+
+		guid = md->guid->guid;
+		idx = guid_defs_find(defs, num_defs, guid);
+		if (idx == -1) {
+			idx = num_defs++;
+			defs[idx].guid = guid;
+			defs[idx].pg = md->guid;
+		}
+
+		if (block->is_builtin)
+			defs[idx].from_builtin = true;
+		else
+			defs[idx].from_json = true;
+
+		defs[idx].platform_group = md->platform_group;
+		defs[idx].name = md->group;
+	}
+
+	if (num_defs > 1)
+		qsort(defs, num_defs, sizeof(*defs), guid_defs_cmp_platform_group);
+
+	/* ----- 2) collect GUIDs from system devices (filtered) ----- */
+	sys = calloc(num_sel_devices, sizeof(*sys));
+	if (!sys)
+		return log_ret(-ENOMEM, "could not build guid list");
+
+	for (int i = 0; i < num_sel_devices; i++) {
+		const struct pmt_device *dev = &ctx->devices[dev_indices[i]];
+		uint32_t guid;
+		int idx;
+
+		if (!dev->guid) {
+			log_warn("device %s has no GUID; skipping",
+				 dev->name ? dev->name : "?");
+			continue;
+		}
+
+		guid = dev->guid->guid;
+		idx = guid_sys_find(sys, n_sys, guid);
+
+		if (idx < 0) {
+			idx = n_sys++;
+			sys[idx].guid = guid;
+		}
+		sys[idx].endpoints++;
+	}
+
+	/* ----- 3) print combined system + defs ----- */
+	printf("GUIDs on this system (TELEM):\n\n");
+	printf("  %-*s   %-*s   %-*s   %-*s   %s\n",
+	       GCOL_GUID, "GUID", GCOL_COUNT, "Count",
+	       GCOL_DEF, "Definition", GCOL_PLAT, "Platform",
+	       "PMT Name");
+	printf("  %.*s   %.*s   %.*s   %.*s   %s\n",
+	       GCOL_GUID, "--------",
+	       GCOL_COUNT, "--------",
+	       GCOL_DEF, "----------",
+	       GCOL_PLAT, "--------",
+	       "--------");
+
+	if (!n_sys) {
+		printf("  (none)\n");
+		return 0;
+	}
+
+	for (int i = 0; i < n_sys; i++) {
+		struct guid_sys *gs = &sys[i];
+		int idx = guid_defs_find(defs, num_defs, gs->guid);
+		const struct pmt_guid *pg = pmt_guid_lookup(gs->guid);
+		const char *pmt_name = (pg && pg->name && *pg->name) ? pg->name : "---";
+		struct guid_defs *gd;
+
+		printf("  %08x   %-*d   ", gs->guid, GCOL_COUNT, gs->endpoints);
+
+		if (idx < 0) {
+			printf("%-*s   %-*s   %s\n",
+			       GCOL_DEF, "---",
+			       GCOL_PLAT, "---",
+			       pmt_name);
+			continue;
+		}
+
+		gd = &defs[idx];
+
+		if (gd->from_builtin || gd->from_json) {
+			bool need_comma = false;
+
+			if (gd->from_builtin) {
+				printf("%-*s", GCOL_DEF, "builtin");
+				need_comma = true;
+			}
+
+			if (gd->from_json) {
+				if (need_comma)
+					fputc(',', stdout);
+				printf("%-*s", GCOL_DEF, "json");
+			}
+		} else {
+			printf("%-*s", GCOL_DEF, "unknown");
+		}
+
+		if (gd->platform_group && !is_blank_string(gd->platform_group))
+			printf("   %-*s", GCOL_PLAT, gd->platform_group);
+		else
+			printf("   %-*s", GCOL_PLAT, "---");
+
+		printf("   %s\n", pmt_name);
+	}
+
+	return 0;
+}
+
+#define PLAT_W 12
+static void print_guid_group_line(uint32_t guid, const struct guid_defs *gd)
+{
+	const char *plat = (gd && gd->platform_group) ? gd->platform_group : "---";
+	const struct pmt_guid *pg = gd ? gd->pg : pmt_guid_lookup(guid);
+	const char *pmt_name = (pg && pg->name && *pg->name) ? pg->name : "---";
+
+	printf("  GUID %08x  platform: %-*s  name: %s\n", guid, PLAT_W, plat, pmt_name);
+
+	if (pg && pg->description && *pg->description)
+		printf("    %s\n", pg->description);
+}
+
+#define NAME_W 24
+static void print_dev_entry(const struct pmt_device *dev)
+{
+	if (dev->pkg_id >= 0 && dev->die_id >= 0)
+		printf("    %-*s  pkg=%d die=%d\n", NAME_W, dev->name, dev->pkg_id, dev->die_id);
+	else
+		printf("    %s\n", dev->name);
+}
+
+static int do_list_devices(const int *dev_indices, int num_sel_devices)
+{
+	const struct pmtctl_context *ctx = pmtctl_get_ctx();
+	auto_free struct guid_defs *defs = NULL;
+	auto_free uint32_t *guid_order = NULL;
+	int num_defs = 0;
+	int num_guids = 0;
+
+	if (ctx->num_devices == 0 || num_sel_devices == 0) {
+		printf("No PMT devices found.\n");
+		return 0;
+	}
+
+	/* Build GUID -> platform_group/name map from metric defs */
+	if (ctx->metrics.total > 0) {
+		defs = calloc(ctx->metrics.total, sizeof(*defs));
+		if (!defs)
+			return log_ret(-ENOMEM, "could not allocate guid info");
+
+		for (int i = 0; i < ctx->metrics.total; i++) {
+			const struct pmt_metric_def *md = pmt_metrics_at(&ctx->metrics, i);
+			uint32_t guid;
+			int idx;
+
+			if (!md || !md->guid)
+				continue;
+
+			guid = md->guid->guid;
+			idx = guid_defs_find(defs, num_defs, guid);
+			if (idx == -1) {
+				idx = num_defs++;
+				defs[idx].guid = guid;
+				defs[idx].pg = md->guid;
+			}
+
+			defs[idx].platform_group = md->platform_group;
+			defs[idx].name = md->group;
+		}
+	}
+
+	/* Collect unique GUIDs in selected-device order */
+	guid_order = calloc(num_sel_devices, sizeof(*guid_order));
+	if (!guid_order)
+		return log_ret(-ENOMEM, "could not allocate guid order");
+
+	for (int k = 0; k < num_sel_devices; k++) {
+		const struct pmt_device *dev = &ctx->devices[dev_indices[k]];
+		uint32_t g;
+		bool found = false;
+
+		if (!dev->guid) {
+			log_warn("device %s has no GUID; skipping", dev->name ? dev->name : "?");
+			continue;
+		}
+
+		g = dev->guid->guid;
+
+		for (int j = 0; j < num_guids; j++) {
+			if (guid_order[j] == g) {
+				found = true;
+				break;
+			}
+		}
+
+		if (!found)
+			guid_order[num_guids++] = g;
+	}
+
+	printf("PMT devices on this system:\n\n");
+
+	for (int g = 0; g < num_guids; g++) {
+		uint32_t guid = guid_order[g];
+		int idx = defs ? guid_defs_find(defs, num_defs, guid) : -1;
+
+		print_guid_group_line(guid, idx >= 0 ? &defs[idx] : NULL);
+
+		for (int k = 0; k < num_sel_devices; k++) {
+			const struct pmt_device *dev = &ctx->devices[dev_indices[k]];
+
+			if (!dev->guid || dev->guid->guid != guid)
+				continue;
+
+			print_dev_entry(dev);
+		}
+		printf("\n");
+	}
+
+	return 0;
+}
+
+static int get_term_columns(void)
+{
+	const char *col_env = getenv("COLUMNS");
+	struct winsize ws;
+
+	if (col_env) {
+		int n = atoi(col_env);
+
+		if (n > 0)
+			return n;
+	}
+#ifdef TIOCGWINSZ
+	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col)
+		return ws.ws_col;
+#endif
+	return LIST_DEFAULT_COLS;
+}
+
+/*
+ * Print @s word-wrapped at @term_cols, with every line (including the first)
+ * indented by @indent spaces.  Mirrors the wordwrap() behaviour in
+ * tools/perf/builtin-list.c: words are broken at spaces/tabs; explicit
+ * newlines in the source text force a break and restart the indent.
+ */
+static void print_wrapped_desc(FILE *out, const char *s, int indent, int term_cols)
+{
+	int column = indent;
+	bool at_line_start = true;
+
+	fprintf(out, "%*s", indent, "");
+
+	while (*s) {
+		int wlen = strcspn(s, " \t\n");
+
+		/* Wrap if the next word would overflow the right margin. */
+		if (!at_line_start && column + 1 + wlen > term_cols) {
+			fprintf(out, "\n%*s", indent, "");
+			column = indent;
+			at_line_start = true;
+		}
+
+		if (!at_line_start) {
+			fputc(' ', out);
+			column++;
+		}
+
+		fprintf(out, "%.*s", wlen, s);
+		column += wlen;
+		at_line_start = false;
+		s += wlen;
+
+		/* Skip spaces/tabs; honour explicit newlines. */
+		while (*s == ' ' || *s == '\t')
+			s++;
+		if (*s == '\n') {
+			fprintf(out, "\n%*s", indent, "");
+			column = indent;
+			at_line_start = true;
+			s++;
+		}
+	}
+	fputc('\n', out);
+}
+
+static void print_group_header(FILE *out, const char *device_name, const char *platform_group,
+			       const char *name, const char *description, int term_cols)
+{
+	const char *pg = (platform_group && *platform_group) ? platform_group : "---";
+	const char *nm = (name && *name)                     ? name           : "---";
+
+	fprintf(out, "TELEM %s   %s  %s\n", device_name, pg, nm);
+	if (description && *description)
+		print_wrapped_desc(out, description, LIST_BASE_INDENT, term_cols);
+}
+
+static void print_group_separator(FILE *out, int width)
+{
+	if (width < 8)
+		width = 8;
+	if (width > 100)
+		width = 100;
+
+	fprintf(out, "%*s", LIST_BASE_INDENT, "");
+
+	for (int i = 0; i < width; i++)
+		fputc('-', out);
+	fputc('\n', out);
+}
+
+/*
+ * The caller will free(*out) with free().
+ */
+static int build_list_view(struct list_metric_view **out, int *out_nr, const int *dev_indices,
+			   int num_sel_devices)
+{
+	const struct pmtctl_context *ctx = pmtctl_get_ctx();
+	const struct pmt_binding *b  = ctx->bindings;
+	struct list_metric_view *arr;
+	int nb = ctx->num_bindings;
+	int arr_count = 0;
+
+	if (ctx->metrics.total == 0)
+		return log_ret(PMTCTL_ERR_CMD_LIST, "no metrics loaded");
+
+	if (!nb)
+		return 0;
+
+	*out = NULL;
+	*out_nr = 0;
+
+	arr = calloc(nb, sizeof(*arr));
+	if (!arr)
+		return -ENOMEM;
+
+	for (int i = 0; i < nb; i++) {
+		const struct pmt_metric_def *md  = pmt_metrics_at(&ctx->metrics, b[i].metric_idx);
+		const struct pmt_device *dev = &ctx->devices[b[i].device_idx];
+		struct list_metric_view *m;
+		bool device_selected = false;
+		char buf[16];
+
+		if (!md || !dev)
+			continue;
+
+		if (!dev->guid) {
+			log_warn("device %s has no GUID; skipping", dev->name ? dev->name : "?");
+			continue;
+		}
+
+		/* Check if this device is in the selected device list */
+		for (int j = 0; j < num_sel_devices; j++) {
+			if (dev_indices[j] == b[i].device_idx) {
+				device_selected = true;
+				break;
+			}
+		}
+
+		if (!device_selected)
+			continue;
+
+		m = &arr[arr_count++];
+
+		snprintf(buf, sizeof(buf), "%08x", dev->guid->guid);
+		m->device_name = strdup(buf);
+
+		m->name = md->event_name;
+		m->desc = md->description;
+		m->platform_group = md->platform_group;
+		m->group_name = (md->guid && md->guid->name && *md->guid->name)
+				? md->guid->name : NULL;
+		m->group_description = (md->guid && md->guid->description && *md->guid->description)
+				? md->guid->description : NULL;
+	}
+
+	*out = arr;
+	*out_nr = arr_count;
+
+	return 0;
+}
+
+/* sort by device_name then metric name */
+static int list_metric_cmp(const void *a, const void *b)
+{
+	const struct list_metric_view *ma = a;
+	const struct list_metric_view *mb = b;
+	int d;
+
+	d = strcmp(ma->device_name, mb->device_name);
+	if (d)
+		return d;
+
+	return strcmp(ma->name, mb->name);
+}
+
+static int do_list_metrics(const struct pmt_global_opts *gopts, const int *dev_indices,
+			   int num_sel_devices)
+{
+	FILE *out = pmtctl_start_pager(gopts);
+	auto_free struct list_metric_view *mview = NULL;
+	int gstart = 0;
+	bool first_group = true;
+	int nr = 0;
+	int ret;
+
+	ret = build_list_view(&mview, &nr, dev_indices, num_sel_devices);
+	if (ret)
+		return ret;
+
+	if (!mview) {
+		log_warn("no metrics to view");
+		return 0;
+	}
+
+	if (!nr) {
+		log_warn("no metrics to view after filtering");
+		return 0;
+	}
+
+	qsort(mview, nr, sizeof(mview[0]), list_metric_cmp);
+
+	int term_cols = get_term_columns();
+
+	/* Iterate group-by-group (group = device_name) */
+
+	while (gstart < nr) {
+		int gend = gstart + 1;
+		int max_name = (int)strlen(mview[gstart].name);
+
+		while (gend < nr && strcmp(mview[gend].device_name,
+					   mview[gstart].device_name) == 0) {
+			int n = (int)strlen(mview[gend].name);
+
+			if (n > max_name)
+				max_name = n;
+			gend++;
+		}
+
+		if (!first_group)
+			fputc('\n', out);
+		first_group = false;
+
+		print_group_header(out,
+				   mview[gstart].device_name,
+				   mview[gstart].platform_group,
+				   mview[gstart].group_name,
+				   mview[gstart].group_description,
+				   term_cols);
+
+		print_group_separator(out, max_name + LIST_CONT_INDENT);
+
+		for (int i = gstart; i < gend; i++) {
+			struct list_metric_view *m = &mview[i];
+
+			fprintf(out, "%*s%s\n", LIST_BASE_INDENT, "", m->name);
+			if (m->desc && *m->desc) {
+				print_wrapped_desc(out, m->desc,
+						   LIST_BASE_INDENT + LIST_CONT_INDENT,
+						   term_cols);
+			}
+		}
+
+		gstart = gend;
+	}
+
+	for (int i = 0; i < nr; i++)
+		free((char *)mview[i].device_name);
+
+	pmtctl_finish_pager(out);
+
+	return 0;
+}
+
+static void list_usage(FILE *out)
+{
+	fprintf(out,
+		"Usage: pmtctl list [options]\n"
+		"\n"
+		"List PMT metrics and optionally devices.\n"
+		"\n"
+		"Options:\n"
+		"  -h, --help              Show this help.\n"
+		"  -d, --device <selector> Restrict to a single endpoint.\n"
+		"                          Can be supplied globally before the command or\n"
+		"                          locally after the command as a fallback.\n"
+		"                          Global value takes precedence when both are given.\n"
+		"                          Selectors: guid=<hex>, ep=<name>\n"
+		"      --devices           List devices only (no metrics).\n"
+		"      --guids             List GUIDs from defs and system.\n"
+		"  -X, --keep              Keep the last pager screen on exit.\n");
+}
+
+#define OPT_LIST_DEVICES 1000
+#define OPT_LIST_GUIDS   1001
+
+static int parse_list_opts(struct list_opts *opts, int argc, char **argv)
+{
+	int c;
+
+	memset(opts, 0, sizeof(*opts));
+	optind = 1;
+
+	static const struct option long_opts[] = {
+		{ "help",    no_argument,       NULL, 'h' },
+		{ "device",  required_argument, NULL, 'd' },
+		{ "devices", no_argument,       NULL, OPT_LIST_DEVICES },
+		{ "guids",   no_argument,       NULL, OPT_LIST_GUIDS },
+		{ "keep",    no_argument,       NULL, 'X' },
+		{ 0, 0, 0, 0 }
+	};
+
+	while ((c = getopt_long(argc, argv, "hd:X", long_opts, NULL)) != -1) {
+		switch (c) {
+		case 'h':
+			opts->show_help = true;
+			break;
+		case 'd':
+			opts->device_selector = optarg;
+			break;
+		case OPT_LIST_DEVICES: /* --devices */
+			opts->devices_only = true;
+			break;
+		case OPT_LIST_GUIDS:  /* --guids */
+			opts->guids_only = true;
+			break;
+		case 'X':
+			opts->keep_screen = true;
+			break;
+		case '?':
+		default:
+			return -EINVAL;
+		}
+	}
+
+	if (optind != argc)
+		return log_ret(-EINVAL, "unexpected extra arguments to 'list'");
+
+	return 0;
+}
+
+int cmd_list(int argc, char **argv, const struct pmt_global_opts *gopts)
+{
+	const struct pmtctl_context *ctx;
+	struct pmt_ep_selector sel;
+	struct list_opts opts;
+	auto_free int *dev_indices = NULL;
+	int num_sel_devices;
+	int ret;
+
+	ret = parse_list_opts(&opts, argc, argv);
+	if (ret) {
+		list_usage(stderr);
+		return PMTCTL_ERR_CMD_PARSE;
+	}
+
+	/*
+	 * Compatibility fallback: prefer global device selector if provided;
+	 * otherwise use command-local --device if supplied after the command.
+	 */
+	if (gopts && gopts->device_selector)
+		opts.device_selector = gopts->device_selector;
+
+	if (opts.show_help) {
+		list_usage(stdout);
+		return 0;
+	}
+
+	if (opts.keep_screen) {
+		const char *less = getenv("LESS");
+		char buf[512];
+
+		if (less && *less)
+			snprintf(buf, sizeof(buf), "-X %s", less);
+		else
+			snprintf(buf, sizeof(buf), "-X");
+		setenv("LESS", buf, 1);
+	}
+
+	/* Initialize pmtctl to get device context */
+	ret = pmtctl_init(gopts);
+	if (ret)
+		return ret;
+
+	/* Apply device selector filter (must precede --devices, --guids,
+	 * and metric listing so all three honour the global -d selector). */
+	ctx = pmtctl_get_ctx();
+
+	dev_indices = calloc(ctx->num_devices, sizeof(*dev_indices));
+	if (!dev_indices)
+		return log_ret(-ENOMEM, "failed to allocate device indices");
+
+	if (opts.device_selector) {
+		ret = pmtctl_parse_ep_selector(opts.device_selector, &sel);
+		if (ret < 0)
+			return ret;
+
+		num_sel_devices = pmt_select_devices(ctx, &sel, dev_indices,
+						     ctx->num_devices);
+		if (num_sel_devices < 0)
+			return num_sel_devices;
+
+		if (num_sel_devices == 0) {
+			return log_ret(PMTCTL_ERR_CMD_LIST, "no devices match selector '%s'",
+				       opts.device_selector);
+		}
+	} else {
+		/* No selector: use all devices */
+		for (int i = 0; i < ctx->num_devices; i++)
+			dev_indices[i] = i;
+		num_sel_devices = ctx->num_devices;
+	}
+
+	if (opts.devices_only)
+		return do_list_devices(dev_indices, num_sel_devices);
+
+	if (opts.guids_only)
+		return do_list_guids(dev_indices, num_sel_devices);
+
+	return do_list_metrics(gopts, dev_indices, num_sel_devices);
+}
diff --git a/tools/arch/x86/pmtctl/src/main.c b/tools/arch/x86/pmtctl/src/main.c
index e93b544d9343..d9666956c27b 100644
--- a/tools/arch/x86/pmtctl/src/main.c
+++ b/tools/arch/x86/pmtctl/src/main.c
@@ -42,6 +42,11 @@ static void print_usage(FILE *out)
 		"\n"
 		"  -q, --quiet             Suppress non-essential messages\n"
 		"      --debug             Enable debug logging\n"
+		"\n"
+		"Commands:\n"
+		"  list      List available PMT devices and metrics\n"
+		"\n"
+		"Run 'pmtctl <command> --help' for command-specific options.\n"
 	);
 }
 
@@ -64,6 +69,8 @@ static int cmd_dispatch(int argc, char **argv)
 	const char *cmd;
 	int option_index = 0;
 	int opt;
+	int cmd_argc;
+	char **cmd_argv;
 
 	while ((opt = getopt_long(argc, argv, "+hVJ:qd:", long_options, &option_index)) != -1) {
 		switch (opt) {
@@ -103,6 +110,12 @@ static int cmd_dispatch(int argc, char **argv)
 	}
 
 	cmd = argv[optind];
+	cmd_argc = argc - optind;
+	cmd_argv = &argv[optind];
+
+	if (!strcmp(cmd, "list"))
+		return cmd_list(cmd_argc, cmd_argv, &gopts);
+
 	if (!strcmp(cmd, "--help") || !strcmp(cmd, "help")) {
 		print_usage(stdout);
 		return 0;
-- 
2.43.0


  parent reply	other threads:[~2026-05-26  1:47 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-26  1:46 [PATCH 00/17] tools/arch/x86/pmtctl: Add Intel PMT command-line tool David E. Box
2026-05-26  1:46 ` [PATCH 01/17] tools/arch/x86/pmtctl: Add MAINTAINERS entry David E. Box
2026-05-26  1:47 ` [PATCH 02/17] tools/arch/x86/pmtctl: Add libpmtctl shared type enumerations David E. Box
2026-05-26  9:20   ` Ilpo Järvinen
2026-05-26  1:47 ` [PATCH 03/17] tools/arch/x86/pmtctl: Add libpmtctl internal logging and utility functions David E. Box
2026-05-26  9:59   ` Ilpo Järvinen
2026-05-26  1:47 ` [PATCH 04/17] tools/arch/x86/pmtctl: Add libpmtctl metric definition database David E. Box
2026-05-26 10:06   ` Ilpo Järvinen
2026-05-26  1:47 ` [PATCH 05/17] tools/arch/x86/pmtctl: Add libpmtctl device enumeration backend David E. Box
2026-05-26 10:35   ` Ilpo Järvinen
2026-05-26  1:47 ` [PATCH 06/17] tools/arch/x86/pmtctl: Add libpmtctl built-in metric provider David E. Box
2026-05-26  1:47 ` [PATCH 07/17] tools/arch/x86/pmtctl: Add libpmtctl JSON " David E. Box
2026-05-26 11:04   ` Ilpo Järvinen
2026-05-26  1:47 ` [PATCH 08/17] tools/arch/x86/pmtctl: Add libpmtctl public API and context David E. Box
2026-05-26 11:25   ` Ilpo Järvinen
2026-05-26 17:44     ` David Box
2026-05-26  1:47 ` [PATCH 09/17] tools/arch/x86/pmtctl: Add libpmtctl Makefile + pc + README David E. Box
2026-05-26  1:47 ` [PATCH 10/17] tools/arch/x86/pmtctl: Add libpmtctl usage sample David E. Box
2026-05-26  1:47 ` [PATCH 11/17] tools/arch/x86/pmtctl: Add libpmtctl built-in metric definition support David E. Box
2026-05-26  1:47 ` [PATCH 12/17] tools/arch/x86/pmtctl: Add pmtctl CLI entry point and pager David E. Box
2026-05-26  1:47 ` David E. Box [this message]
2026-05-26  1:47 ` [PATCH 14/17] tools/arch/x86/pmtctl: Add pmtctl 'stat' command David E. Box
2026-05-26  1:47 ` [PATCH 15/17] tools/arch/x86/pmtctl: Add pmtxml2json conversion tool David E. Box
2026-05-26  1:47 ` [PATCH 16/17] tools/arch/x86/pmtctl: Add README.md David E. Box
2026-05-26  1:47 ` [PATCH 17/17] tools/arch/x86/pmtctl: Add man page David E. Box

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260526014719.2248380-14-david.e.box@linux.intel.com \
    --to=david.e.box@linux.intel.com \
    --cc=andriy.shevchenko@linux.intel.com \
    --cc=ilpo.jarvinen@linux.intel.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=platform-driver-x86@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox