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
next prev 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