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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.