summaryrefslogtreecommitdiffstats
path: root/tools/perf/util/pmu.c
diff options
context:
space:
mode:
authorJoel Fernandes <joelagnelf@nvidia.com>2026-07-18 13:15:36 -0400
committerPaul E. McKenney <paulmck@kernel.org>2026-07-23 10:39:57 -0700
commit34ff636f322c6d5e4f0a52338fa63a7dff09a495 (patch)
tree7629759c1ec7148b95bc8ab3080df20a5ecd2273 /tools/perf/util/pmu.c
downloadlinux-stable-34ff636f322c6d5e4f0a52338fa63a7dff09a495.tar.gz
linux-stable-34ff636f322c6d5e4f0a52338fa63a7dff09a495.zip
scftorture: Count single_rpc offline failures in statistics outputgrafted
scf_torture_stats_print() aggregates each invoker thread's counters into a local scf_statistics structure before printing, but the n_single_rpc_ofl field is missing from the aggregation loop. As a result, the "single_rpc_ofl" value printed in the statistics line is always zero, even when smp_call_function_single() invocations for the RPC test have failed due to offline CPUs and been counted by the invoker threads. Add the missing accumulation so that the printed value reflects the actual counts. Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com> Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Diffstat (limited to 'tools/perf/util/pmu.c')
-rw-r--r--tools/perf/util/pmu.c2786
1 files changed, 2786 insertions, 0 deletions
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
new file mode 100644
index 000000000..a550f030b
--- /dev/null
+++ b/tools/perf/util/pmu.c
@@ -0,0 +1,2786 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/list.h>
+#include <linux/compiler.h>
+#include <linux/string.h>
+#include <linux/zalloc.h>
+#include <linux/ctype.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <dirent.h>
+#include <api/fs/fs.h>
+#include <api/io.h>
+#include <api/io_dir.h>
+#include <locale.h>
+#include <fnmatch.h>
+#include <math.h>
+#include "debug.h"
+#include "evsel.h"
+#include "pmu.h"
+#include "drm_pmu.h"
+#include "hwmon_pmu.h"
+#include "pmus.h"
+#include "tool_pmu.h"
+#include "tp_pmu.h"
+#include <util/pmu-bison.h>
+#include <util/pmu-flex.h>
+#include "parse-events.h"
+#include "print-events.h"
+#include "hashmap.h"
+#include "header.h"
+#include "string2.h"
+#include "strbuf.h"
+#include "fncache.h"
+#include "util/evsel_config.h"
+#include <regex.h>
+
+#define UNIT_MAX_LEN 31 /* max length for event unit name */
+
+enum event_source {
+ /* An event loaded from /sys/bus/event_source/devices/<pmu>/events. */
+ EVENT_SRC_SYSFS,
+ /* An event loaded from a CPUID matched json file. */
+ EVENT_SRC_CPU_JSON,
+ /*
+ * An event loaded from a /sys/bus/event_source/devices/<pmu>/identifier matched json
+ * file.
+ */
+ EVENT_SRC_SYS_JSON,
+};
+
+/**
+ * struct perf_pmu_alias - An event either read from sysfs or builtin in
+ * pmu-events.c, created by parsing the pmu-events json files.
+ */
+struct perf_pmu_alias {
+ /** @name: Name of the event like "mem-loads". */
+ char *name;
+ /** @desc: Optional short description of the event. */
+ char *desc;
+ /** @long_desc: Optional long description. */
+ char *long_desc;
+ /**
+ * @topic: Optional topic such as cache or pipeline, particularly for
+ * json events.
+ */
+ char *topic;
+ /** @terms: Owned copy of the event terms. */
+ char *terms;
+ /**
+ * @legacy_terms: If the event aliases a legacy event, holds a copy
+ * ofthe legacy event string.
+ */
+ char *legacy_terms;
+ /**
+ * @pmu_name: The name copied from the json struct pmu_event. This can
+ * differ from the PMU name as it won't have suffixes.
+ */
+ char *pmu_name;
+ /** @unit: Units for the event, such as bytes or cache lines. */
+ char unit[UNIT_MAX_LEN+1];
+ /** @scale: Value to scale read counter values by. */
+ double scale;
+ /** @retirement_latency_mean: Value to be given for unsampled retirement latency mean. */
+ double retirement_latency_mean;
+ /** @retirement_latency_min: Value to be given for unsampled retirement latency min. */
+ double retirement_latency_min;
+ /** @retirement_latency_max: Value to be given for unsampled retirement latency max. */
+ double retirement_latency_max;
+ /**
+ * @per_pkg: Does the file
+ * <sysfs>/bus/event_source/devices/<pmu_name>/events/<name>.per-pkg or
+ * equivalent json value exist and have the value 1.
+ */
+ bool per_pkg;
+ /**
+ * @snapshot: Does the file
+ * <sysfs>/bus/event_source/devices/<pmu_name>/events/<name>.snapshot
+ * exist and have the value 1.
+ */
+ bool snapshot;
+ /**
+ * @deprecated: Is the event hidden and so not shown in perf list by
+ * default.
+ */
+ bool deprecated;
+ /**
+ * @legacy_deprecated_checked: Legacy events may not be supported by the
+ * PMU need to be checked. If they aren't supported they are marked
+ * deprecated.
+ */
+ bool legacy_deprecated_checked;
+ /** @from_sysfs: Was the alias from sysfs or a json event? */
+ bool from_sysfs;
+ /** @info_loaded: Have the scale, unit and other values been read from disk? */
+ bool info_loaded;
+};
+
+static int pmu_aliases_parse(struct perf_pmu *pmu);
+
+static struct perf_pmu_format *perf_pmu__new_format(struct list_head *list, char *name)
+{
+ struct perf_pmu_format *format;
+
+ format = zalloc(sizeof(*format));
+ if (!format)
+ return NULL;
+
+ format->name = strdup(name);
+ if (!format->name) {
+ free(format);
+ return NULL;
+ }
+ list_add_tail(&format->list, list);
+ return format;
+}
+
+/* Called at the end of parsing a format. */
+void perf_pmu_format__set_value(void *vformat, int config, unsigned long *bits)
+{
+ struct perf_pmu_format *format = vformat;
+
+ format->value = config;
+ memcpy(format->bits, bits, sizeof(format->bits));
+}
+
+static void __perf_pmu_format__load(struct perf_pmu_format *format, FILE *file)
+{
+ void *scanner;
+ int ret;
+
+ ret = perf_pmu_lex_init(&scanner);
+ if (ret)
+ return;
+
+ perf_pmu_set_in(file, scanner);
+ ret = perf_pmu_parse(format, scanner);
+ perf_pmu_lex_destroy(scanner);
+ format->loaded = true;
+}
+
+static void perf_pmu_format__load(const struct perf_pmu *pmu, struct perf_pmu_format *format)
+{
+ char path[PATH_MAX];
+ FILE *file = NULL;
+
+ if (format->loaded)
+ return;
+
+ if (!perf_pmu__pathname_scnprintf(path, sizeof(path), pmu->name, "format"))
+ return;
+
+ assert(strlen(path) + strlen(format->name) + 2 < sizeof(path));
+ strcat(path, "/");
+ strcat(path, format->name);
+
+ file = fopen(path, "r");
+ if (!file)
+ return;
+ __perf_pmu_format__load(format, file);
+ fclose(file);
+}
+
+/*
+ * Parse & process all the sysfs attributes located under
+ * the directory specified in 'dir' parameter.
+ */
+static int perf_pmu__format_parse(struct perf_pmu *pmu, int dirfd, bool eager_load)
+{
+ struct io_dirent64 *evt_ent;
+ struct io_dir format_dir;
+ int ret = 0;
+
+ io_dir__init(&format_dir, dirfd);
+
+ while ((evt_ent = io_dir__readdir(&format_dir)) != NULL) {
+ struct perf_pmu_format *format;
+ char *name = evt_ent->d_name;
+
+ if (io_dir__is_dir(&format_dir, evt_ent))
+ continue;
+
+ format = perf_pmu__new_format(&pmu->format, name);
+ if (!format) {
+ ret = -ENOMEM;
+ break;
+ }
+
+ if (eager_load) {
+ FILE *file;
+ int fd = openat(dirfd, name, O_RDONLY);
+
+ if (fd < 0) {
+ ret = -errno;
+ break;
+ }
+ file = fdopen(fd, "r");
+ if (!file) {
+ close(fd);
+ break;
+ }
+ __perf_pmu_format__load(format, file);
+ fclose(file);
+ }
+ }
+
+ close(format_dir.dirfd);
+ return ret;
+}
+
+/*
+ * Reading/parsing the default pmu format definition, which should be
+ * located at:
+ * /sys/bus/event_source/devices/<dev>/format as sysfs group attributes.
+ */
+static int pmu_format(struct perf_pmu *pmu, int dirfd, const char *name, bool eager_load)
+{
+ int fd;
+
+ fd = perf_pmu__pathname_fd(dirfd, name, "format", O_DIRECTORY);
+ if (fd < 0)
+ return 0;
+
+ /* it'll close the fd */
+ if (perf_pmu__format_parse(pmu, fd, eager_load))
+ return -1;
+
+ return 0;
+}
+
+static int parse_double(const char *scale, char **end, double *sval)
+{
+ char *lc;
+ int ret = 0;
+
+ /*
+ * save current locale
+ */
+ lc = setlocale(LC_NUMERIC, NULL);
+
+ /*
+ * The lc string may be allocated in static storage,
+ * so get a dynamic copy to make it survive setlocale
+ * call below.
+ */
+ lc = strdup(lc);
+ if (!lc) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /*
+ * force to C locale to ensure kernel
+ * scale string is converted correctly.
+ * kernel uses default C locale.
+ */
+ setlocale(LC_NUMERIC, "C");
+
+ *sval = strtod(scale, end);
+
+out:
+ /* restore locale */
+ setlocale(LC_NUMERIC, lc);
+ free(lc);
+ return ret;
+}
+
+int perf_pmu__convert_scale(const char *scale, char **end, double *sval)
+{
+ return parse_double(scale, end, sval);
+}
+
+static int perf_pmu__parse_scale(struct perf_pmu *pmu, struct perf_pmu_alias *alias)
+{
+ struct stat st;
+ ssize_t sret;
+ size_t len;
+ char scale[128];
+ int fd, ret = -1;
+ char path[PATH_MAX];
+
+ len = perf_pmu__event_source_devices_scnprintf(path, sizeof(path));
+ if (!len)
+ return 0;
+ scnprintf(path + len, sizeof(path) - len, "%s/events/%s.scale", pmu->name, alias->name);
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return -1;
+
+ if (fstat(fd, &st) < 0)
+ goto error;
+
+ sret = read(fd, scale, sizeof(scale)-1);
+ if (sret <= 0)
+ goto error;
+
+ if (scale[sret - 1] == '\n')
+ scale[sret - 1] = '\0';
+ else
+ scale[sret] = '\0';
+
+ ret = perf_pmu__convert_scale(scale, NULL, &alias->scale);
+error:
+ close(fd);
+ return ret;
+}
+
+static int perf_pmu__parse_unit(struct perf_pmu *pmu, struct perf_pmu_alias *alias)
+{
+ char path[PATH_MAX];
+ size_t len;
+ ssize_t sret;
+ int fd;
+
+
+ len = perf_pmu__event_source_devices_scnprintf(path, sizeof(path));
+ if (!len)
+ return 0;
+ scnprintf(path + len, sizeof(path) - len, "%s/events/%s.unit", pmu->name, alias->name);
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return -1;
+
+ sret = read(fd, alias->unit, UNIT_MAX_LEN);
+ if (sret <= 0)
+ goto error;
+
+ close(fd);
+
+ if (alias->unit[sret - 1] == '\n')
+ alias->unit[sret - 1] = '\0';
+ else
+ alias->unit[sret] = '\0';
+
+ return 0;
+error:
+ close(fd);
+ alias->unit[0] = '\0';
+ return -1;
+}
+
+static bool perf_pmu__parse_event_source_bool(const char *pmu_name, const char *event_name,
+ const char *suffix)
+{
+ char path[PATH_MAX];
+ size_t len;
+ int fd;
+
+ len = perf_pmu__event_source_devices_scnprintf(path, sizeof(path));
+ if (!len)
+ return false;
+
+ scnprintf(path + len, sizeof(path) - len, "%s/events/%s.%s", pmu_name, event_name, suffix);
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return false;
+
+#ifndef NDEBUG
+ {
+ char buf[8];
+
+ len = read(fd, buf, sizeof(buf));
+ assert(len == 1 || len == 2);
+ assert(buf[0] == '1');
+ }
+#endif
+
+ close(fd);
+ return true;
+}
+
+static void perf_pmu__parse_per_pkg(struct perf_pmu *pmu, struct perf_pmu_alias *alias)
+{
+ alias->per_pkg = perf_pmu__parse_event_source_bool(pmu->name, alias->name, "per-pkg");
+}
+
+static void perf_pmu__parse_snapshot(struct perf_pmu *pmu, struct perf_pmu_alias *alias)
+{
+ alias->snapshot = perf_pmu__parse_event_source_bool(pmu->name, alias->name, "snapshot");
+}
+
+/* Delete an alias entry. */
+static void perf_pmu_free_alias(struct perf_pmu_alias *alias)
+{
+ if (!alias)
+ return;
+
+ zfree(&alias->name);
+ zfree(&alias->desc);
+ zfree(&alias->long_desc);
+ zfree(&alias->topic);
+ zfree(&alias->pmu_name);
+ zfree(&alias->terms);
+ zfree(&alias->legacy_terms);
+ free(alias);
+}
+
+static void perf_pmu__del_aliases(struct perf_pmu *pmu)
+{
+ struct hashmap_entry *entry;
+ size_t bkt;
+
+ if (!pmu->aliases)
+ return;
+
+ hashmap__for_each_entry(pmu->aliases, entry, bkt)
+ perf_pmu_free_alias(entry->pvalue);
+
+ hashmap__free(pmu->aliases);
+ pmu->aliases = NULL;
+}
+
+static struct perf_pmu_alias *perf_pmu__find_alias(struct perf_pmu *pmu,
+ const char *name,
+ bool load)
+{
+ struct perf_pmu_alias *alias;
+ bool has_sysfs_event;
+ char event_file_name[NAME_MAX + 8];
+
+ if (hashmap__find(pmu->aliases, name, &alias))
+ return alias;
+
+ if (!load || pmu->sysfs_aliases_loaded)
+ return NULL;
+
+ /*
+ * Test if alias/event 'name' exists in the PMU's sysfs/events
+ * directory. If not skip parsing the sysfs aliases. Sysfs event
+ * name must be all lower or all upper case.
+ */
+ scnprintf(event_file_name, sizeof(event_file_name), "events/%s", name);
+ for (size_t i = 7, n = 7 + strlen(name); i < n; i++)
+ event_file_name[i] = tolower(event_file_name[i]);
+
+ has_sysfs_event = perf_pmu__file_exists(pmu, event_file_name);
+ if (!has_sysfs_event) {
+ for (size_t i = 7, n = 7 + strlen(name); i < n; i++)
+ event_file_name[i] = toupper(event_file_name[i]);
+
+ has_sysfs_event = perf_pmu__file_exists(pmu, event_file_name);
+ }
+ if (has_sysfs_event) {
+ pmu_aliases_parse(pmu);
+ if (hashmap__find(pmu->aliases, name, &alias))
+ return alias;
+ }
+
+ return NULL;
+}
+
+static bool assign_str(const char *name, const char *field, char **old_str,
+ const char *new_str)
+{
+ if (!*old_str && new_str) {
+ *old_str = strdup(new_str);
+ return true;
+ }
+
+ if (!new_str || !strcasecmp(*old_str, new_str))
+ return false; /* Nothing to update. */
+
+ pr_debug("alias %s differs in field '%s' ('%s' != '%s')\n",
+ name, field, *old_str, new_str);
+ zfree(old_str);
+ *old_str = strdup(new_str);
+ return true;
+}
+
+static void read_alias_info(struct perf_pmu *pmu, struct perf_pmu_alias *alias)
+{
+ if (!alias->from_sysfs || alias->info_loaded)
+ return;
+
+ /*
+ * load unit name and scale if available
+ */
+ perf_pmu__parse_unit(pmu, alias);
+ perf_pmu__parse_scale(pmu, alias);
+ perf_pmu__parse_per_pkg(pmu, alias);
+ perf_pmu__parse_snapshot(pmu, alias);
+}
+
+struct update_alias_data {
+ struct perf_pmu *pmu;
+ struct perf_pmu_alias *alias;
+ bool legacy;
+};
+
+static int update_alias(const struct pmu_event *pe,
+ const struct pmu_events_table *table __maybe_unused,
+ void *vdata)
+{
+ struct update_alias_data *data = vdata;
+ int ret = 0;
+
+ read_alias_info(data->pmu, data->alias);
+ assign_str(pe->name, "desc", &data->alias->desc, pe->desc);
+ assign_str(pe->name, "long_desc", &data->alias->long_desc, pe->long_desc);
+ assign_str(pe->name, "topic", &data->alias->topic, pe->topic);
+ data->alias->per_pkg = pe->perpkg;
+ if (pe->event) {
+ if (data->legacy) {
+ zfree(&data->alias->legacy_terms);
+ data->alias->legacy_terms = strdup(pe->event);
+ } else {
+ zfree(&data->alias->terms);
+ data->alias->terms = strdup(pe->event);
+ }
+ }
+ if (!ret && pe->unit) {
+ char *unit;
+
+ ret = perf_pmu__convert_scale(pe->unit, &unit, &data->alias->scale);
+ if (!ret)
+ snprintf(data->alias->unit, sizeof(data->alias->unit), "%s", unit);
+ }
+ if (!ret && pe->retirement_latency_mean) {
+ ret = parse_double(pe->retirement_latency_mean, NULL,
+ &data->alias->retirement_latency_mean);
+ }
+ if (!ret && pe->retirement_latency_min) {
+ ret = parse_double(pe->retirement_latency_min, NULL,
+ &data->alias->retirement_latency_min);
+ }
+ if (!ret && pe->retirement_latency_max) {
+ ret = parse_double(pe->retirement_latency_max, NULL,
+ &data->alias->retirement_latency_max);
+ }
+ return ret;
+}
+
+static int perf_pmu__new_alias(struct perf_pmu *pmu, const char *name,
+ const char *desc, const char *val, int val_fd,
+ const struct pmu_event *pe, enum event_source src)
+{
+ struct perf_pmu_alias *alias, *old_alias;
+ int ret = 0;
+ const char *long_desc = NULL, *topic = NULL, *unit = NULL, *pmu_name = NULL;
+ bool deprecated = false, perpkg = false;
+
+ if (perf_pmu__find_alias(pmu, name, /*load=*/ false)) {
+ /* Alias was already created/loaded. */
+ return 0;
+ }
+
+ if (pe) {
+ long_desc = pe->long_desc;
+ topic = pe->topic;
+ unit = pe->unit;
+ perpkg = pe->perpkg;
+ deprecated = pe->deprecated;
+ if (pe->pmu && strcmp(pe->pmu, "default_core"))
+ pmu_name = pe->pmu;
+ }
+
+ alias = zalloc(sizeof(*alias));
+ if (!alias)
+ return -ENOMEM;
+
+ alias->scale = 1.0;
+ alias->unit[0] = '\0';
+ alias->per_pkg = perpkg;
+ alias->snapshot = false;
+ alias->deprecated = deprecated;
+ alias->retirement_latency_mean = 0.0;
+ alias->retirement_latency_min = 0.0;
+ alias->retirement_latency_max = 0.0;
+
+ if (!ret && pe && pe->retirement_latency_mean) {
+ ret = parse_double(pe->retirement_latency_mean, NULL,
+ &alias->retirement_latency_mean);
+ }
+ if (!ret && pe && pe->retirement_latency_min) {
+ ret = parse_double(pe->retirement_latency_min, NULL,
+ &alias->retirement_latency_min);
+ }
+ if (!ret && pe && pe->retirement_latency_max) {
+ ret = parse_double(pe->retirement_latency_max, NULL,
+ &alias->retirement_latency_max);
+ }
+ if (ret)
+ return ret;
+
+ if (val_fd < 0) {
+ alias->terms = strdup(val);
+ } else {
+ char buf[256];
+ struct io io;
+ size_t line_len;
+
+ io__init(&io, val_fd, buf, sizeof(buf));
+ ret = io__getline(&io, &alias->terms, &line_len) < 0 ? -errno : 0;
+ if (ret) {
+ pr_err("Failed to read alias %s\n", name);
+ return ret;
+ }
+ if (line_len >= 1 && alias->terms[line_len - 1] == '\n')
+ alias->terms[line_len - 1] = '\0';
+ }
+ alias->name = strdup(name);
+ alias->desc = desc ? strdup(desc) : NULL;
+ alias->long_desc = long_desc ? strdup(long_desc) : NULL;
+ alias->topic = topic ? strdup(topic) : NULL;
+ alias->pmu_name = pmu_name ? strdup(pmu_name) : NULL;
+ if (unit) {
+ if (perf_pmu__convert_scale(unit, (char **)&unit, &alias->scale) < 0) {
+ perf_pmu_free_alias(alias);
+ return -1;
+ }
+ snprintf(alias->unit, sizeof(alias->unit), "%s", unit);
+ }
+ switch (src) {
+ default:
+ case EVENT_SRC_SYSFS:
+ alias->from_sysfs = true;
+ if (pmu->events_table || pmu->is_core) {
+ /* Update an event from sysfs with json data. */
+ struct update_alias_data data = {
+ .pmu = pmu,
+ .alias = alias,
+ .legacy = false,
+ };
+ if ((pmu_events_table__find_event(pmu->events_table, pmu, name,
+ update_alias, &data) == 0)) {
+ /*
+ * Override sysfs encodings with json encodings
+ * specific to the cpuid.
+ */
+ pmu->cpu_common_json_aliases++;
+ }
+ if (pmu->is_core) {
+ /* Add in legacy encodings. */
+ data.legacy = true;
+ if (pmu_events_table__find_event(
+ perf_pmu__default_core_events_table(),
+ pmu, name, update_alias, &data) == 0)
+ pmu->cpu_common_json_aliases++;
+ }
+ }
+ pmu->sysfs_aliases++;
+ break;
+ case EVENT_SRC_CPU_JSON:
+ pmu->cpu_json_aliases++;
+ break;
+ case EVENT_SRC_SYS_JSON:
+ pmu->sys_json_aliases++;
+ break;
+
+ }
+ hashmap__set(pmu->aliases, alias->name, alias, /*old_key=*/ NULL, &old_alias);
+ perf_pmu_free_alias(old_alias);
+ return 0;
+}
+
+static inline bool pmu_alias_info_file(const char *name)
+{
+ size_t len;
+
+ len = strlen(name);
+ if (len > 5 && !strcmp(name + len - 5, ".unit"))
+ return true;
+ if (len > 6 && !strcmp(name + len - 6, ".scale"))
+ return true;
+ if (len > 8 && !strcmp(name + len - 8, ".per-pkg"))
+ return true;
+ if (len > 9 && !strcmp(name + len - 9, ".snapshot"))
+ return true;
+
+ return false;
+}
+
+/*
+ * Reading the pmu event aliases definition, which should be located at:
+ * /sys/bus/event_source/devices/<dev>/events as sysfs group attributes.
+ */
+static int __pmu_aliases_parse(struct perf_pmu *pmu, int events_dir_fd)
+{
+ struct io_dirent64 *evt_ent;
+ struct io_dir event_dir;
+
+ io_dir__init(&event_dir, events_dir_fd);
+
+ while ((evt_ent = io_dir__readdir(&event_dir))) {
+ char *name = evt_ent->d_name;
+ int fd;
+
+ if (!strcmp(name, ".") || !strcmp(name, ".."))
+ continue;
+
+ /*
+ * skip info files parsed in perf_pmu__new_alias()
+ */
+ if (pmu_alias_info_file(name))
+ continue;
+
+ fd = openat(events_dir_fd, name, O_RDONLY);
+ if (fd == -1) {
+ pr_debug("Cannot open %s\n", name);
+ continue;
+ }
+
+ if (perf_pmu__new_alias(pmu, name, /*desc=*/ NULL,
+ /*val=*/ NULL, fd, /*pe=*/ NULL,
+ EVENT_SRC_SYSFS) < 0)
+ pr_debug("Cannot set up %s\n", name);
+ close(fd);
+ }
+
+ pmu->sysfs_aliases_loaded = true;
+ return 0;
+}
+
+static int pmu_aliases_parse(struct perf_pmu *pmu)
+{
+ char path[PATH_MAX];
+ size_t len;
+ int events_dir_fd, ret;
+
+ if (pmu->sysfs_aliases_loaded)
+ return 0;
+
+ len = perf_pmu__event_source_devices_scnprintf(path, sizeof(path));
+ if (!len)
+ return 0;
+ scnprintf(path + len, sizeof(path) - len, "%s/events", pmu->name);
+
+ events_dir_fd = open(path, O_DIRECTORY);
+ if (events_dir_fd == -1) {
+ pmu->sysfs_aliases_loaded = true;
+ return 0;
+ }
+ ret = __pmu_aliases_parse(pmu, events_dir_fd);
+ close(events_dir_fd);
+ return ret;
+}
+
+static int pmu_aliases_parse_eager(struct perf_pmu *pmu, int sysfs_fd)
+{
+ char path[NAME_MAX + 8];
+ int ret, events_dir_fd;
+
+ scnprintf(path, sizeof(path), "%s/events", pmu->name);
+ events_dir_fd = openat(sysfs_fd, path, O_DIRECTORY, 0);
+ if (events_dir_fd == -1) {
+ pmu->sysfs_aliases_loaded = true;
+ return 0;
+ }
+ ret = __pmu_aliases_parse(pmu, events_dir_fd);
+ close(events_dir_fd);
+ return ret;
+}
+
+static int pmu_alias_terms(struct perf_pmu_alias *alias, struct list_head *terms)
+{
+ struct parse_events_terms alias_terms;
+ struct parse_events_term *term;
+ int ret;
+
+ parse_events_terms__init(&alias_terms);
+ ret = parse_events_terms(&alias_terms, alias->terms);
+ if (ret) {
+ pr_err("Cannot parse '%s' terms '%s': %d\n",
+ alias->name, alias->terms, ret);
+ parse_events_terms__exit(&alias_terms);
+ return ret;
+ }
+ list_for_each_entry(term, &alias_terms.terms, list) {
+ /*
+ * Weak terms don't override command line options,
+ * which we don't want for implicit terms in aliases.
+ */
+ term->weak = true;
+ }
+ list_splice_init(&alias_terms.terms, terms);
+ parse_events_terms__exit(&alias_terms);
+ return 0;
+}
+
+/*
+ * Uncore PMUs have a "cpumask" file under sysfs. CPU PMUs (e.g. on arm/arm64)
+ * may have a "cpus" file.
+ */
+static struct perf_cpu_map *pmu_cpumask(int dirfd, const char *pmu_name, bool is_core)
+{
+ const char *templates[] = {
+ "cpumask",
+ "cpus",
+ NULL
+ };
+ const char **template;
+
+ for (template = templates; *template; template++) {
+ struct io io;
+ char buf[128];
+ char *cpumask = NULL;
+ size_t cpumask_len;
+ ssize_t ret;
+ struct perf_cpu_map *cpus;
+
+ io.fd = perf_pmu__pathname_fd(dirfd, pmu_name, *template, O_RDONLY);
+ if (io.fd < 0)
+ continue;
+
+ io__init(&io, io.fd, buf, sizeof(buf));
+ ret = io__getline(&io, &cpumask, &cpumask_len);
+ close(io.fd);
+ if (ret < 0)
+ continue;
+
+ cpus = perf_cpu_map__new(cpumask);
+ free(cpumask);
+ if (cpus)
+ return cpus;
+ }
+
+ /* Nothing found, for core PMUs assume this means all CPUs. */
+ return is_core ? cpu_map__online() : NULL;
+}
+
+static bool pmu_is_uncore(int dirfd, const char *name)
+{
+ int fd;
+
+ fd = perf_pmu__pathname_fd(dirfd, name, "cpumask", O_PATH);
+ if (fd < 0)
+ return false;
+
+ close(fd);
+ return true;
+}
+
+static char *pmu_id(const char *name)
+{
+ char path[PATH_MAX], *str;
+ size_t len;
+
+ perf_pmu__pathname_scnprintf(path, sizeof(path), name, "identifier");
+
+ if (filename__read_str(path, &str, &len) < 0)
+ return NULL;
+
+ /* empty identifier file — nothing useful */
+ if (len == 0) {
+ free(str);
+ return NULL;
+ }
+
+ str[len - 1] = 0; /* remove line feed */
+
+ return str;
+}
+
+/**
+ * is_sysfs_pmu_core() - PMU CORE devices have different name other than cpu in
+ * sysfs on some platforms like ARM or Intel hybrid. Looking for
+ * possible the cpus file in sysfs files to identify whether this is a
+ * core device.
+ * @name: The PMU name such as "cpu_atom".
+ */
+static int is_sysfs_pmu_core(const char *name)
+{
+ char path[PATH_MAX];
+
+ if (!perf_pmu__pathname_scnprintf(path, sizeof(path), name, "cpus"))
+ return 0;
+ return file_available(path);
+}
+
+/**
+ * Return the length of the PMU name not including the suffix for uncore PMUs.
+ *
+ * We want to deduplicate many similar uncore PMUs by stripping their suffixes,
+ * but there are never going to be too many core PMUs and the suffixes might be
+ * interesting. "arm_cortex_a53" vs "arm_cortex_a57" or "cpum_cf" for example.
+ *
+ * @skip_duplicate_pmus: False in verbose mode so all uncore PMUs are visible
+ */
+static size_t pmu_deduped_name_len(const struct perf_pmu *pmu, const char *name,
+ bool skip_duplicate_pmus)
+{
+ return skip_duplicate_pmus && !pmu->is_core
+ ? pmu_name_len_no_suffix(name)
+ : strlen(name);
+}
+
+/**
+ * perf_pmu__match_wildcard - Does the pmu_name start with tok and is then only
+ * followed by nothing or a suffix? tok may contain
+ * part of a suffix.
+ * @pmu_name: The pmu_name with possible suffix.
+ * @tok: The wildcard argument to match.
+ */
+static bool perf_pmu__match_wildcard(const char *pmu_name, const char *tok)
+{
+ const char *p, *suffix;
+ bool has_hex = false;
+ bool has_underscore = false;
+ size_t tok_len = strlen(tok);
+
+ /* Check start of pmu_name for equality. */
+ if (strncmp(pmu_name, tok, tok_len))
+ return false;
+
+ suffix = p = pmu_name + tok_len;
+ if (*p == 0)
+ return true;
+
+ /* Ensure we end in a number or a mix of number and "_". */
+ while (1) {
+ if (!has_underscore && (*p == '_')) {
+ has_underscore = true;
+ ++p;
+ ++suffix;
+ }
+
+ if (!isxdigit(*p))
+ return false;
+ if (!has_hex)
+ has_hex = !isdigit(*p);
+ if (*(++p) == 0)
+ break;
+ }
+
+ if (has_hex)
+ return (p - suffix) > 2;
+
+ return true;
+}
+
+/**
+ * perf_pmu__match_ignoring_suffix_uncore - Does the pmu_name match tok ignoring
+ * any trailing suffix on pmu_name and
+ * tok? The Suffix must be in form
+ * tok_{digits}, or tok{digits}.
+ * @pmu_name: The pmu_name with possible suffix.
+ * @tok: The possible match to pmu_name.
+ */
+static bool perf_pmu__match_ignoring_suffix_uncore(const char *pmu_name, const char *tok)
+{
+ size_t pmu_name_len, tok_len;
+
+ /* For robustness, check for NULL. */
+ if (pmu_name == NULL)
+ return tok == NULL;
+
+ /* uncore_ prefixes are ignored. */
+ if (!strncmp(pmu_name, "uncore_", 7))
+ pmu_name += 7;
+ if (!strncmp(tok, "uncore_", 7))
+ tok += 7;
+
+ pmu_name_len = pmu_name_len_no_suffix(pmu_name);
+ tok_len = pmu_name_len_no_suffix(tok);
+ if (pmu_name_len != tok_len)
+ return false;
+
+ return strncmp(pmu_name, tok, pmu_name_len) == 0;
+}
+
+
+/**
+ * perf_pmu__match_wildcard_uncore - does to_match match the PMU's name?
+ * @pmu_name: The pmu->name or pmu->alias to match against.
+ * @to_match: the json struct pmu_event name. This may lack a suffix (which
+ * matches) or be of the form "socket,pmuname" which will match
+ * "socketX_pmunameY".
+ */
+static bool perf_pmu__match_wildcard_uncore(const char *pmu_name, const char *to_match)
+{
+ char *mutable_to_match, *tok, *tmp;
+
+ if (!pmu_name)
+ return false;
+
+ /* uncore_ prefixes are ignored. */
+ if (!strncmp(pmu_name, "uncore_", 7))
+ pmu_name += 7;
+ if (!strncmp(to_match, "uncore_", 7))
+ to_match += 7;
+
+ if (strchr(to_match, ',') == NULL)
+ return perf_pmu__match_wildcard(pmu_name, to_match);
+
+ /* Process comma separated list of PMU name components. */
+ mutable_to_match = strdup(to_match);
+ if (!mutable_to_match)
+ return false;
+
+ tok = strtok_r(mutable_to_match, ",", &tmp);
+ while (tok) {
+ size_t tok_len = strlen(tok);
+
+ if (strncmp(pmu_name, tok, tok_len)) {
+ /* Mismatch between part of pmu_name and tok. */
+ free(mutable_to_match);
+ return false;
+ }
+ /* Move pmu_name forward over tok and suffix. */
+ pmu_name += tok_len;
+ while (*pmu_name != '\0' && isdigit(*pmu_name))
+ pmu_name++;
+ if (*pmu_name == '_')
+ pmu_name++;
+
+ tok = strtok_r(NULL, ",", &tmp);
+ }
+ free(mutable_to_match);
+ return *pmu_name == '\0';
+}
+
+bool pmu_uncore_identifier_match(const char *compat, const char *id)
+{
+ regex_t re;
+ regmatch_t pmatch[1];
+ int match;
+
+ if (regcomp(&re, compat, REG_EXTENDED) != 0) {
+ /* Warn unable to generate match particular string. */
+ pr_info("Invalid regular expression %s\n", compat);
+ return false;
+ }
+
+ match = !regexec(&re, id, 1, pmatch, 0);
+ if (match) {
+ /* Ensure a full match. */
+ match = pmatch[0].rm_so == 0 && (size_t)pmatch[0].rm_eo == strlen(id);
+ }
+ regfree(&re);
+
+ return match;
+}
+
+static int pmu_add_cpu_aliases_map_callback(const struct pmu_event *pe,
+ const struct pmu_events_table *table __maybe_unused,
+ void *vdata)
+{
+ struct perf_pmu *pmu = vdata;
+
+ perf_pmu__new_alias(pmu, pe->name, pe->desc, pe->event, /*val_fd=*/ -1,
+ pe, EVENT_SRC_CPU_JSON);
+ return 0;
+}
+
+/*
+ * From the pmu_events_table, find the events that correspond to the given
+ * PMU and add them to the list 'head'.
+ */
+void pmu_add_cpu_aliases_table(struct perf_pmu *pmu, const struct pmu_events_table *table)
+{
+ pmu_events_table__for_each_event(table, pmu, pmu_add_cpu_aliases_map_callback, pmu);
+}
+
+static void pmu_add_cpu_aliases(struct perf_pmu *pmu)
+{
+ if (!pmu->events_table && !pmu->is_core)
+ return;
+
+ if (pmu->cpu_aliases_added)
+ return;
+
+ pmu_add_cpu_aliases_table(pmu, pmu->events_table);
+ if (pmu->is_core)
+ pmu_add_cpu_aliases_table(pmu, perf_pmu__default_core_events_table());
+
+ pmu->cpu_aliases_added = true;
+}
+
+static int pmu_add_sys_aliases_iter_fn(const struct pmu_event *pe,
+ const struct pmu_events_table *table __maybe_unused,
+ void *vdata)
+{
+ struct perf_pmu *pmu = vdata;
+
+ if (!pe->compat || !pe->pmu) {
+ /* No data to match. */
+ return 0;
+ }
+
+ if (!perf_pmu__match_wildcard_uncore(pmu->name, pe->pmu) &&
+ !perf_pmu__match_wildcard_uncore(pmu->alias_name, pe->pmu)) {
+ /* PMU name/alias_name don't match. */
+ return 0;
+ }
+
+ if (pmu_uncore_identifier_match(pe->compat, pmu->id)) {
+ /* Id matched. */
+ perf_pmu__new_alias(pmu,
+ pe->name,
+ pe->desc,
+ pe->event,
+ /*val_fd=*/ -1,
+ pe,
+ EVENT_SRC_SYS_JSON);
+ }
+ return 0;
+}
+
+void pmu_add_sys_aliases(struct perf_pmu *pmu)
+{
+ if (!pmu->id)
+ return;
+
+ pmu_for_each_sys_event(pmu_add_sys_aliases_iter_fn, pmu);
+}
+
+static char *pmu_find_alias_name(struct perf_pmu *pmu, int dirfd)
+{
+ FILE *file = perf_pmu__open_file_at(pmu, dirfd, "alias");
+ char *line = NULL;
+ size_t line_len = 0;
+ ssize_t ret;
+
+ if (!file)
+ return NULL;
+
+ ret = getline(&line, &line_len, file);
+ if (ret < 0) {
+ fclose(file);
+ return NULL;
+ }
+ /* Remove trailing newline. */
+ if (ret > 0 && line[ret - 1] == '\n')
+ line[--ret] = '\0';
+
+ fclose(file);
+ return line;
+}
+
+static int pmu_max_precise(int dirfd, struct perf_pmu *pmu)
+{
+ int max_precise = -1;
+
+ perf_pmu__scan_file_at(pmu, dirfd, "caps/max_precise", "%d", &max_precise);
+ return max_precise;
+}
+
+void __weak
+perf_pmu__arch_init(struct perf_pmu *pmu)
+{
+ if (pmu->is_core)
+ pmu->mem_events = perf_mem_events;
+}
+
+/* Variant of str_hash that does tolower on each character. */
+static size_t aliases__hash(long key, void *ctx __maybe_unused)
+{
+ const char *s = (const char *)key;
+ size_t h = 0;
+
+ while (*s) {
+ h = h * 31 + tolower(*s);
+ s++;
+ }
+ return h;
+}
+
+static bool aliases__equal(long key1, long key2, void *ctx __maybe_unused)
+{
+ return strcasecmp((const char *)key1, (const char *)key2) == 0;
+}
+
+int perf_pmu__init(struct perf_pmu *pmu, __u32 type, const char *name)
+{
+ pmu->type = type;
+ INIT_LIST_HEAD(&pmu->format);
+ INIT_LIST_HEAD(&pmu->caps);
+
+ pmu->name = strdup(name);
+ if (!pmu->name)
+ return -ENOMEM;
+
+ pmu->aliases = hashmap__new(aliases__hash, aliases__equal, /*ctx=*/ NULL);
+ if (!pmu->aliases)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static __u32 wellknown_pmu_type(const char *pmu_name)
+{
+ struct {
+ const char *pmu_name;
+ __u32 type;
+ } wellknown_pmus[] = {
+ {
+ "software",
+ PERF_TYPE_SOFTWARE
+ },
+ {
+ "tracepoint",
+ PERF_TYPE_TRACEPOINT
+ },
+ {
+ "breakpoint",
+ PERF_TYPE_BREAKPOINT
+ },
+ };
+ for (size_t i = 0; i < ARRAY_SIZE(wellknown_pmus); i++) {
+ if (!strcmp(wellknown_pmus[i].pmu_name, pmu_name))
+ return wellknown_pmus[i].type;
+ }
+ return PERF_TYPE_MAX;
+}
+
+struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char *name,
+ bool eager_load)
+{
+ struct perf_pmu *pmu;
+
+ pmu = zalloc(sizeof(*pmu));
+ if (!pmu)
+ return NULL;
+
+ if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, name) != 0) {
+ perf_pmu__delete(pmu);
+ return NULL;
+ }
+
+ /*
+ * Read type early to fail fast if a lookup name isn't a PMU. Ensure
+ * that type value is successfully assigned (return 1).
+ */
+ if (perf_pmu__scan_file_at(pmu, dirfd, "type", "%u", &pmu->type) != 1) {
+ /* Double check the PMU's name isn't wellknown. */
+ pmu->type = wellknown_pmu_type(name);
+ if (pmu->type == PERF_TYPE_MAX) {
+ perf_pmu__delete(pmu);
+ return NULL;
+ }
+ }
+
+ /*
+ * The pmu data we store & need consists of the pmu
+ * type value and format definitions. Load both right
+ * now.
+ */
+ if (pmu_format(pmu, dirfd, name, eager_load)) {
+ perf_pmu__delete(pmu);
+ return NULL;
+ }
+
+ pmu->is_core = is_pmu_core(name);
+ pmu->cpus = pmu_cpumask(dirfd, name, pmu->is_core);
+
+ pmu->is_uncore = pmu_is_uncore(dirfd, name);
+ if (pmu->is_uncore)
+ pmu->id = pmu_id(name);
+ pmu->max_precise = pmu_max_precise(dirfd, pmu);
+ pmu->alias_name = pmu_find_alias_name(pmu, dirfd);
+ pmu->events_table = perf_pmu__find_events_table(pmu);
+ /*
+ * Load the sys json events/aliases when loading the PMU as each event
+ * may have a different compat regular expression. We therefore can't
+ * know the number of sys json events/aliases without computing the
+ * regular expressions for them all.
+ */
+ pmu_add_sys_aliases(pmu);
+ list_add_tail(&pmu->list, pmus);
+
+ perf_pmu__arch_init(pmu);
+
+ if (eager_load)
+ pmu_aliases_parse_eager(pmu, dirfd);
+
+ return pmu;
+}
+
+/* Creates the PMU when sysfs scanning fails. */
+struct perf_pmu *perf_pmu__create_placeholder_core_pmu(struct list_head *core_pmus)
+{
+ struct perf_pmu *pmu = zalloc(sizeof(*pmu));
+
+ if (!pmu)
+ return NULL;
+
+ pmu->name = strdup("cpu");
+ if (!pmu->name) {
+ free(pmu);
+ return NULL;
+ }
+
+ pmu->is_core = true;
+ pmu->type = PERF_TYPE_RAW;
+ pmu->cpus = cpu_map__online();
+
+ INIT_LIST_HEAD(&pmu->format);
+ pmu->aliases = hashmap__new(aliases__hash, aliases__equal, /*ctx=*/ NULL);
+ INIT_LIST_HEAD(&pmu->caps);
+ list_add_tail(&pmu->list, core_pmus);
+ return pmu;
+}
+
+bool perf_pmu__is_fake(const struct perf_pmu *pmu)
+{
+ return pmu->type == PERF_PMU_TYPE_FAKE;
+}
+
+void perf_pmu__warn_invalid_formats(struct perf_pmu *pmu)
+{
+ struct perf_pmu_format *format;
+
+ if (pmu->formats_checked)
+ return;
+
+ pmu->formats_checked = true;
+
+ /* fake pmu doesn't have format list */
+ if (perf_pmu__is_fake(pmu))
+ return;
+
+ list_for_each_entry(format, &pmu->format, list) {
+ perf_pmu_format__load(pmu, format);
+ if (format->value >= PERF_PMU_FORMAT_VALUE_CONFIG_END) {
+ pr_warning("WARNING: '%s' format '%s' requires 'perf_event_attr::config%d'"
+ "which is not supported by this version of perf!\n",
+ pmu->name, format->name, format->value);
+ return;
+ }
+ }
+}
+
+/*
+ * Unpacks a raw config[n] value using the sparse bitfield that defines a
+ * format attr. For example "config1:1,6-7,44" defines a 4 bit value across non
+ * contiguous bits and this function returns those 4 bits as a value.
+ */
+u64 perf_pmu__format_unpack(unsigned long *format, u64 config_val)
+{
+ int val_bit = 0;
+ u64 res = 0;
+ int fmt_bit;
+
+ for_each_set_bit(fmt_bit, format, PERF_PMU_FORMAT_BITS) {
+ if (config_val & (1ULL << fmt_bit))
+ res |= BIT_ULL(val_bit);
+
+ val_bit++;
+ }
+ return res;
+}
+
+struct perf_pmu_format *pmu_find_format(const struct list_head *formats,
+ const char *name)
+{
+ struct perf_pmu_format *format;
+
+ list_for_each_entry(format, formats, list)
+ if (!strcmp(format->name, name))
+ return format;
+
+ return NULL;
+}
+
+__u64 perf_pmu__format_bits(const struct perf_pmu *pmu, const char *name)
+{
+ struct perf_pmu_format *format = pmu_find_format(&pmu->format, name);
+ __u64 bits = 0;
+ int fbit;
+
+ if (!format)
+ return 0;
+
+ for_each_set_bit(fbit, format->bits, PERF_PMU_FORMAT_BITS)
+ bits |= 1ULL << fbit;
+
+ return bits;
+}
+
+int perf_pmu__format_type(const struct perf_pmu *pmu, const char *name)
+{
+ struct perf_pmu_format *format = pmu_find_format(&pmu->format, name);
+
+ if (!format)
+ return -1;
+
+ perf_pmu_format__load(pmu, format);
+ return format->value;
+}
+
+/*
+ * Sets value based on the format definition (format parameter)
+ * and unformatted value (value parameter).
+ */
+void perf_pmu__format_pack(unsigned long *format, __u64 value, __u64 *v,
+ bool zero)
+{
+ unsigned long fbit, vbit;
+
+ for (fbit = 0, vbit = 0; fbit < PERF_PMU_FORMAT_BITS; fbit++) {
+
+ if (!test_bit(fbit, format))
+ continue;
+
+ if (value & (1llu << vbit++))
+ *v |= (1llu << fbit);
+ else if (zero)
+ *v &= ~(1llu << fbit);
+ }
+}
+
+static __u64 pmu_format_max_value(const unsigned long *format)
+{
+ int w;
+
+ w = bitmap_weight(format, PERF_PMU_FORMAT_BITS);
+ if (!w)
+ return 0;
+ if (w < 64)
+ return (1ULL << w) - 1;
+ return -1;
+}
+
+/*
+ * Term is a string term, and might be a param-term. Try to look up it's value
+ * in the remaining terms.
+ * - We have a term like "base-or-format-term=param-term",
+ * - We need to find the value supplied for "param-term" (with param-term named
+ * in a config string) later on in the term list.
+ */
+static int pmu_resolve_param_term(struct parse_events_term *term,
+ struct parse_events_terms *head_terms,
+ __u64 *value)
+{
+ struct parse_events_term *t;
+
+ list_for_each_entry(t, &head_terms->terms, list) {
+ if (t->type_val == PARSE_EVENTS__TERM_TYPE_NUM &&
+ t->config && !strcmp(t->config, term->config)) {
+ t->used = true;
+ *value = t->val.num;
+ return 0;
+ }
+ }
+
+ if (verbose > 0)
+ printf("Required parameter '%s' not specified\n", term->config);
+
+ return -1;
+}
+
+static char *pmu_formats_string(const struct list_head *formats)
+{
+ struct perf_pmu_format *format;
+ char *str = NULL;
+ struct strbuf buf = STRBUF_INIT;
+ unsigned int i = 0;
+
+ if (!formats)
+ return NULL;
+
+ /* sysfs exported terms */
+ list_for_each_entry(format, formats, list)
+ if (strbuf_addf(&buf, i++ ? ",%s" : "%s", format->name) < 0)
+ goto error;
+
+ str = strbuf_detach(&buf, NULL);
+error:
+ strbuf_release(&buf);
+
+ return str;
+}
+
+/*
+ * Setup one of config[12] attr members based on the
+ * user input data - term parameter.
+ */
+static int pmu_config_term(const struct perf_pmu *pmu,
+ struct perf_event_attr *attr,
+ struct parse_events_term *term,
+ struct parse_events_terms *head_terms,
+ bool zero, bool apply_hardcoded,
+ struct parse_events_error *err)
+{
+ struct perf_pmu_format *format;
+ __u64 *vp;
+ __u64 val, max_val;
+
+ /*
+ * If this is a parameter we've already used for parameterized-eval,
+ * skip it in normal eval.
+ */
+ if (term->used)
+ return 0;
+
+ /*
+ * Hardcoded terms are generally handled in event parsing, which
+ * traditionally have had to handle not having a PMU. An alias may
+ * have hard coded config values, optionally apply them below.
+ */
+ if (parse_events__is_hardcoded_term(term)) {
+ /* Config terms set all bits in the config. */
+ DECLARE_BITMAP(bits, PERF_PMU_FORMAT_BITS);
+
+ if (!apply_hardcoded)
+ return 0;
+
+ bitmap_fill(bits, PERF_PMU_FORMAT_BITS);
+
+ switch (term->type_term) {
+ case PARSE_EVENTS__TERM_TYPE_CONFIG:
+ assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM);
+ perf_pmu__format_pack(bits, term->val.num, &attr->config, zero);
+ break;
+ case PARSE_EVENTS__TERM_TYPE_CONFIG1:
+ assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM);
+ perf_pmu__format_pack(bits, term->val.num, &attr->config1, zero);
+ break;
+ case PARSE_EVENTS__TERM_TYPE_CONFIG2:
+ assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM);
+ perf_pmu__format_pack(bits, term->val.num, &attr->config2, zero);
+ break;
+ case PARSE_EVENTS__TERM_TYPE_CONFIG3:
+ assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM);
+ perf_pmu__format_pack(bits, term->val.num, &attr->config3, zero);
+ break;
+ case PARSE_EVENTS__TERM_TYPE_CONFIG4:
+ assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM);
+ perf_pmu__format_pack(bits, term->val.num, &attr->config4, zero);
+ break;
+ case PARSE_EVENTS__TERM_TYPE_LEGACY_HARDWARE_CONFIG:
+ assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM);
+ assert(term->val.num < PERF_COUNT_HW_MAX);
+ assert(pmu->is_core);
+ attr->config = term->val.num;
+ if (perf_pmus__supports_extended_type())
+ attr->config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT;
+ attr->type = PERF_TYPE_HARDWARE;
+ break;
+ case PARSE_EVENTS__TERM_TYPE_LEGACY_CACHE_CONFIG: {
+#ifndef NDEBUG
+ int cache_type = term->val.num & 0xFF;
+ int cache_op = (term->val.num >> 8) & 0xFF;
+ int cache_result = (term->val.num >> 16) & 0xFF;
+
+ assert(cache_type < PERF_COUNT_HW_CACHE_MAX);
+ assert(cache_op < PERF_COUNT_HW_CACHE_OP_MAX);
+ assert(cache_result < PERF_COUNT_HW_CACHE_RESULT_MAX);
+#endif
+ assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM);
+ assert((term->val.num & ~0xFFFFFF) == 0);
+ assert(pmu->is_core);
+ attr->config = term->val.num;
+ if (perf_pmus__supports_extended_type())
+ attr->config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT;
+ attr->type = PERF_TYPE_HW_CACHE;
+ break;
+ }
+ case PARSE_EVENTS__TERM_TYPE_USER: /* Not hardcoded. */
+ return -EINVAL;
+ case PARSE_EVENTS__TERM_TYPE_NAME ... PARSE_EVENTS__TERM_TYPE_RATIO_TO_PREV:
+ /* Skip non-config terms. */
+ break;
+ default:
+ break;
+ }
+ return 0;
+ }
+
+ format = pmu_find_format(&pmu->format, term->config);
+ if (!format) {
+ char *pmu_term = pmu_formats_string(&pmu->format);
+ char *unknown_term;
+ char *help_msg;
+
+ if (asprintf(&unknown_term,
+ "unknown term '%s' for pmu '%s'",
+ term->config, pmu->name) < 0)
+ unknown_term = NULL;
+ help_msg = parse_events_formats_error_string(pmu_term);
+ if (err) {
+ parse_events_error__handle(err, term->err_term,
+ unknown_term,
+ help_msg);
+ } else {
+ pr_debug("%s (%s)\n", unknown_term, help_msg);
+ free(unknown_term);
+ }
+ free(pmu_term);
+ return -EINVAL;
+ }
+ perf_pmu_format__load(pmu, format);
+ switch (format->value) {
+ case PERF_PMU_FORMAT_VALUE_CONFIG:
+ vp = &attr->config;
+ break;
+ case PERF_PMU_FORMAT_VALUE_CONFIG1:
+ vp = &attr->config1;
+ break;
+ case PERF_PMU_FORMAT_VALUE_CONFIG2:
+ vp = &attr->config2;
+ break;
+ case PERF_PMU_FORMAT_VALUE_CONFIG3:
+ vp = &attr->config3;
+ break;
+ case PERF_PMU_FORMAT_VALUE_CONFIG4:
+ vp = &attr->config4;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Either directly use a numeric term, or try to translate string terms
+ * using event parameters.
+ */
+ if (term->type_val == PARSE_EVENTS__TERM_TYPE_NUM) {
+ if (term->no_value &&
+ bitmap_weight(format->bits, PERF_PMU_FORMAT_BITS) > 1) {
+ if (err) {
+ parse_events_error__handle(err, term->err_val,
+ strdup("no value assigned for term"),
+ NULL);
+ }
+ return -EINVAL;
+ }
+
+ val = term->val.num;
+ } else if (term->type_val == PARSE_EVENTS__TERM_TYPE_STR) {
+ if (strcmp(term->val.str, "?")) {
+ if (verbose > 0) {
+ pr_info("Invalid sysfs entry %s=%s\n",
+ term->config, term->val.str);
+ }
+ if (err) {
+ parse_events_error__handle(err, term->err_val,
+ strdup("expected numeric value"),
+ NULL);
+ }
+ return -EINVAL;
+ }
+
+ if (pmu_resolve_param_term(term, head_terms, &val))
+ return -EINVAL;
+ } else
+ return -EINVAL;
+
+ max_val = pmu_format_max_value(format->bits);
+ if (val > max_val) {
+ if (err) {
+ char *err_str;
+
+ if (asprintf(&err_str,
+ "value too big for format (%s), maximum is %llu",
+ format->name, (unsigned long long)max_val) < 0) {
+ err_str = strdup("value too big for format");
+ }
+ parse_events_error__handle(err, term->err_val, err_str, /*help=*/NULL);
+ return -EINVAL;
+ }
+ /*
+ * Assume we don't care if !err, in which case the value will be
+ * silently truncated.
+ */
+ }
+
+ perf_pmu__format_pack(format->bits, val, vp, zero);
+ return 0;
+}
+
+int perf_pmu__config_terms(const struct perf_pmu *pmu,
+ struct perf_event_attr *attr,
+ struct parse_events_terms *terms,
+ bool zero, bool apply_hardcoded,
+ struct parse_events_error *err)
+{
+ struct parse_events_term *term;
+
+ if (perf_pmu__is_hwmon(pmu))
+ return hwmon_pmu__config_terms(pmu, attr, terms, err);
+ if (perf_pmu__is_drm(pmu))
+ return drm_pmu__config_terms(pmu, attr, terms, err);
+
+ list_for_each_entry(term, &terms->terms, list) {
+ if (pmu_config_term(pmu, attr, term, terms, zero, apply_hardcoded, err))
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Configures event's 'attr' parameter based on the:
+ * 1) users input - specified in terms parameter
+ * 2) pmu format definitions - specified by pmu parameter
+ */
+int perf_pmu__config(struct perf_pmu *pmu, struct perf_event_attr *attr,
+ struct parse_events_terms *head_terms,
+ bool apply_hardcoded,
+ struct parse_events_error *err)
+{
+ bool zero = !!pmu->perf_event_attr_init_default;
+
+ /* Fake PMU doesn't have proper terms so nothing to configure in attr. */
+ if (perf_pmu__is_fake(pmu))
+ return 0;
+
+ return perf_pmu__config_terms(pmu, attr, head_terms, zero, apply_hardcoded, err);
+}
+
+static struct perf_pmu_alias *pmu_find_alias(struct perf_pmu *pmu,
+ struct parse_events_term *term)
+{
+ struct perf_pmu_alias *alias;
+ const char *name;
+
+ if (parse_events__is_hardcoded_term(term))
+ return NULL;
+
+ if (term->type_val == PARSE_EVENTS__TERM_TYPE_NUM) {
+ if (!term->no_value)
+ return NULL;
+ if (pmu_find_format(&pmu->format, term->config))
+ return NULL;
+ name = term->config;
+
+ } else if (term->type_val == PARSE_EVENTS__TERM_TYPE_STR) {
+ if (strcasecmp(term->config, "event"))
+ return NULL;
+ name = term->val.str;
+ } else {
+ return NULL;
+ }
+
+ alias = perf_pmu__find_alias(pmu, name, /*load=*/ true);
+ if (alias || pmu->cpu_aliases_added)
+ return alias;
+
+ /* Alias doesn't exist, try to get it from the json events. */
+ if ((pmu_events_table__find_event(pmu->events_table, pmu, name,
+ pmu_add_cpu_aliases_map_callback,
+ pmu) == 0) ||
+ (pmu->is_core &&
+ pmu_events_table__find_event(perf_pmu__default_core_events_table(),
+ pmu, name,
+ pmu_add_cpu_aliases_map_callback,
+ pmu) == 0)) {
+ alias = perf_pmu__find_alias(pmu, name, /*load=*/ false);
+ }
+ return alias;
+}
+
+
+static int check_info_data(struct perf_pmu *pmu,
+ struct perf_pmu_alias *alias,
+ struct perf_pmu_info *info,
+ struct parse_events_error *err,
+ int column)
+{
+ read_alias_info(pmu, alias);
+ /*
+ * Only one term in event definition can
+ * define unit, scale and snapshot, fail
+ * if there's more than one.
+ */
+ if (info->unit && alias->unit[0]) {
+ parse_events_error__handle(err, column,
+ strdup("Attempt to set event's unit twice"),
+ NULL);
+ return -EINVAL;
+ }
+ if (info->scale && alias->scale) {
+ parse_events_error__handle(err, column,
+ strdup("Attempt to set event's scale twice"),
+ NULL);
+ return -EINVAL;
+ }
+ if (info->snapshot && alias->snapshot) {
+ parse_events_error__handle(err, column,
+ strdup("Attempt to set event snapshot twice"),
+ NULL);
+ return -EINVAL;
+ }
+
+ if (alias->unit[0])
+ info->unit = alias->unit;
+
+ if (alias->scale)
+ info->scale = alias->scale;
+
+ if (alias->snapshot)
+ info->snapshot = alias->snapshot;
+
+ return 0;
+}
+
+static int perf_pmu__parse_terms_to_attr(struct perf_pmu *pmu, const char *terms_str,
+ struct perf_event_attr *attr)
+{
+ struct parse_events_terms terms;
+ int ret;
+
+ parse_events_terms__init(&terms);
+ ret = parse_events_terms(&terms, terms_str);
+ if (ret) {
+ pr_debug("Failed to parse terms '%s': %d\n", terms_str, ret);
+ parse_events_terms__exit(&terms);
+ return ret;
+ }
+ ret = perf_pmu__config(pmu, attr, &terms, /*apply_hardcoded=*/true, /*err=*/NULL);
+ parse_events_terms__exit(&terms);
+ return ret;
+}
+
+/*
+ * Find alias in the terms list and replace it with the terms
+ * defined for the alias
+ */
+int perf_pmu__check_alias(struct perf_pmu *pmu, struct parse_events_terms *head_terms,
+ struct perf_pmu_info *info, bool *rewrote_terms,
+ u64 *alternate_hw_config, struct parse_events_error *err)
+{
+ struct parse_events_term *term, *h;
+ struct perf_pmu_alias *alias;
+ int ret;
+
+ *rewrote_terms = false;
+ info->per_pkg = false;
+
+ /*
+ * Mark unit and scale as not set
+ * (different from default values, see below)
+ */
+ info->unit = NULL;
+ info->scale = 0.0;
+ info->snapshot = false;
+ info->retirement_latency_mean = 0.0;
+ info->retirement_latency_min = 0.0;
+ info->retirement_latency_max = 0.0;
+
+ if (perf_pmu__is_hwmon(pmu)) {
+ ret = hwmon_pmu__check_alias(head_terms, info, err);
+ goto out;
+ }
+ if (perf_pmu__is_drm(pmu)) {
+ ret = drm_pmu__check_alias(pmu, head_terms, info, err);
+ goto out;
+ }
+
+ /* Fake PMU doesn't rewrite terms. */
+ if (perf_pmu__is_fake(pmu))
+ goto out;
+
+ list_for_each_entry_safe(term, h, &head_terms->terms, list) {
+ alias = pmu_find_alias(pmu, term);
+ if (!alias)
+ continue;
+ ret = pmu_alias_terms(alias, &term->list);
+ if (ret) {
+ parse_events_error__handle(err, term->err_term,
+ strdup("Failed to parse terms"),
+ NULL);
+ return ret;
+ }
+
+ *rewrote_terms = true;
+ ret = check_info_data(pmu, alias, info, err, term->err_term);
+ if (ret)
+ return ret;
+
+ if (alias->legacy_terms) {
+ struct perf_event_attr attr = {.config = 0,};
+
+ ret = perf_pmu__parse_terms_to_attr(pmu, alias->legacy_terms, &attr);
+ if (ret) {
+ parse_events_error__handle(err, term->err_term,
+ strdup("Error evaluating legacy terms"),
+ NULL);
+ return ret;
+ }
+ if (attr.type == PERF_TYPE_HARDWARE)
+ *alternate_hw_config = attr.config & PERF_HW_EVENT_MASK;
+ }
+
+ if (alias->per_pkg)
+ info->per_pkg = true;
+
+ info->retirement_latency_mean = alias->retirement_latency_mean;
+ info->retirement_latency_min = alias->retirement_latency_min;
+ info->retirement_latency_max = alias->retirement_latency_max;
+
+ list_del_init(&term->list);
+ parse_events_term__delete(term);
+ }
+out:
+ /*
+ * if no unit or scale found in aliases, then
+ * set defaults as for evsel
+ * unit cannot left to NULL
+ */
+ if (info->unit == NULL)
+ info->unit = "";
+
+ if (info->scale == 0.0)
+ info->scale = 1.0;
+
+ return 0;
+}
+
+struct find_event_args {
+ const char *event;
+ void *state;
+ pmu_event_callback cb;
+};
+
+static int find_event_callback(void *state, struct pmu_event_info *info)
+{
+ struct find_event_args *args = state;
+
+ if (!strcmp(args->event, info->name))
+ return args->cb(args->state, info);
+
+ return 0;
+}
+
+int perf_pmu__find_event(struct perf_pmu *pmu, const char *event, void *state, pmu_event_callback cb)
+{
+ struct find_event_args args = {
+ .event = event,
+ .state = state,
+ .cb = cb,
+ };
+
+ /* Sub-optimal, but function is only used by tests. */
+ return perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/ false,
+ &args, find_event_callback);
+}
+
+static void perf_pmu__del_formats(struct list_head *formats)
+{
+ struct perf_pmu_format *fmt, *tmp;
+
+ list_for_each_entry_safe(fmt, tmp, formats, list) {
+ list_del(&fmt->list);
+ zfree(&fmt->name);
+ free(fmt);
+ }
+}
+
+bool perf_pmu__has_format(const struct perf_pmu *pmu, const char *name)
+{
+ struct perf_pmu_format *format;
+
+ list_for_each_entry(format, &pmu->format, list) {
+ if (!strcmp(format->name, name))
+ return true;
+ }
+ return false;
+}
+
+int perf_pmu__for_each_format(struct perf_pmu *pmu, void *state, pmu_format_callback cb)
+{
+ static const char *const terms[] = {
+ "config=0..0xffffffffffffffff",
+ "config1=0..0xffffffffffffffff",
+ "config2=0..0xffffffffffffffff",
+ "config3=0..0xffffffffffffffff",
+ "config4=0..0xffffffffffffffff",
+ "legacy-hardware-config=0..9,",
+ "legacy-cache-config=0..0xffffff,",
+ "name=string",
+ "period=number",
+ "freq=number",
+ "branch_type=(u|k|hv|any|...)",
+ "time",
+ "call-graph=(fp|dwarf|lbr)",
+ "stack-size=number",
+ "max-stack=number",
+ "nr=number",
+ "inherit",
+ "no-inherit",
+ "overwrite",
+ "no-overwrite",
+ "percore",
+ "aux-output",
+ "aux-action=(pause|resume|start-paused)",
+ "aux-sample-size=number",
+ "cpu=number",
+ "ratio-to-prev=string",
+ };
+ struct perf_pmu_format *format;
+ int ret;
+
+ /*
+ * max-events and driver-config are missing above as are the internal
+ * types user, metric-id, and raw. Assert against the enum
+ * parse_events__term_type so they are kept in sync.
+ */
+ _Static_assert(ARRAY_SIZE(terms) == __PARSE_EVENTS__TERM_TYPE_NR - 4,
+ "perf_pmu__for_each_format()'s terms must be kept in sync with enum parse_events__term_type");
+ list_for_each_entry(format, &pmu->format, list) {
+ perf_pmu_format__load(pmu, format);
+ ret = cb(state, format->name, (int)format->value, format->bits);
+ if (ret)
+ return ret;
+ }
+ if (!pmu->is_core)
+ return 0;
+
+ for (size_t i = 0; i < ARRAY_SIZE(terms); i++) {
+ int config = PERF_PMU_FORMAT_VALUE_CONFIG;
+
+ if (i < PERF_PMU_FORMAT_VALUE_CONFIG_END)
+ config = i;
+
+ ret = cb(state, terms[i], config, /*bits=*/NULL);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+/**
+ * is_pmu_core() - Check if the given PMU name corresponds to a core CPU PMU.
+ * @name: The PMU name to check.
+ *
+ * Core PMUs can be identified by:
+ * 1. Exact name match:
+ * - "cpu": Typically used on x86 architectures.
+ * - "cpum_cf": Typically used on s390 architectures (CPU Measurement Counter Facility).
+ * - "default_core": A generic name used to refer to the default core PMU.
+ * 2. Sysfs file existence check (is_sysfs_pmu_core):
+ * - Typically used on ARM systems or Intel hybrid architectures (e.g., "cpu_atom",
+ * "cpu_core"). This approach checks if the sysfs directory for the PMU
+ * contains a "cpus" file.
+ */
+bool is_pmu_core(const char *name)
+{
+ return !strcmp(name, "cpu") ||
+ !strcmp(name, "cpum_cf") ||
+ !strcmp(name, "default_core") ||
+ is_sysfs_pmu_core(name);
+}
+
+bool perf_pmu__supports_legacy_cache(const struct perf_pmu *pmu)
+{
+ return pmu->is_core;
+}
+
+bool perf_pmu__auto_merge_stats(const struct perf_pmu *pmu)
+{
+ return !pmu->is_core || perf_pmus__num_core_pmus() == 1;
+}
+
+bool perf_pmu__have_event(struct perf_pmu *pmu, const char *name)
+{
+ if (!name)
+ return false;
+ if (perf_pmu__is_tool(pmu) && tool_pmu__skip_event(name))
+ return false;
+ if (perf_pmu__is_tracepoint(pmu))
+ return tp_pmu__have_event(pmu, name);
+ if (perf_pmu__is_hwmon(pmu))
+ return hwmon_pmu__have_event(pmu, name);
+ if (perf_pmu__is_drm(pmu))
+ return drm_pmu__have_event(pmu, name);
+ if (perf_pmu__find_alias(pmu, name, /*load=*/ true) != NULL)
+ return true;
+ if (pmu->cpu_aliases_added || (!pmu->events_table && !pmu->is_core))
+ return false;
+ if (pmu_events_table__find_event(pmu->events_table, pmu, name, NULL, NULL) == 0)
+ return true;
+ return pmu->is_core &&
+ pmu_events_table__find_event(perf_pmu__default_core_events_table(),
+ pmu, name, NULL, NULL) == 0;
+}
+
+size_t perf_pmu__num_events(struct perf_pmu *pmu)
+{
+ size_t nr;
+
+ if (perf_pmu__is_tracepoint(pmu))
+ return tp_pmu__num_events(pmu);
+ if (perf_pmu__is_hwmon(pmu))
+ return hwmon_pmu__num_events(pmu);
+ if (perf_pmu__is_drm(pmu))
+ return drm_pmu__num_events(pmu);
+
+ pmu_aliases_parse(pmu);
+ nr = pmu->sysfs_aliases + pmu->sys_json_aliases;
+
+ if (pmu->cpu_aliases_added) {
+ nr += pmu->cpu_json_aliases;
+ } else if (pmu->events_table || pmu->is_core) {
+ nr += pmu_events_table__num_events(pmu->events_table, pmu);
+ if (pmu->is_core) {
+ nr += pmu_events_table__num_events(
+ perf_pmu__default_core_events_table(), pmu);
+ }
+ nr -= pmu->cpu_common_json_aliases;
+ } else {
+ assert(pmu->cpu_json_aliases == 0 && pmu->cpu_common_json_aliases == 0);
+ }
+
+ if (perf_pmu__is_tool(pmu))
+ nr -= tool_pmu__num_skip_events();
+
+ return pmu->selectable ? nr + 1 : nr;
+}
+
+static int sub_non_neg(int a, int b)
+{
+ if (b > a)
+ return 0;
+ return a - b;
+}
+
+static char *format_alias(char *buf, int len, const struct perf_pmu *pmu,
+ const struct perf_pmu_alias *alias, bool skip_duplicate_pmus)
+{
+ struct parse_events_terms terms;
+ struct parse_events_term *term;
+ int ret, used;
+ size_t pmu_name_len = pmu_deduped_name_len(pmu, pmu->name,
+ skip_duplicate_pmus);
+
+ /* Paramemterized events have the parameters shown. */
+ if (!strstr(alias->terms, "=?")) {
+ /* No parameters. */
+ snprintf(buf, len, "%.*s/%s/", (int)pmu_name_len, pmu->name, alias->name);
+ return buf;
+ }
+
+ parse_events_terms__init(&terms);
+ ret = parse_events_terms(&terms, alias->terms);
+ if (ret) {
+ pr_err("Failure to parse '%s' terms '%s': %d\n",
+ alias->name, alias->terms, ret);
+ parse_events_terms__exit(&terms);
+ scnprintf(buf, len, "%.*s/%s/", (int)pmu_name_len, pmu->name, alias->name);
+ return buf;
+ }
+ used = scnprintf(buf, len, "%.*s/%s", (int)pmu_name_len, pmu->name, alias->name);
+
+ list_for_each_entry(term, &terms.terms, list) {
+ const char *name = term->config;
+
+ if (!name)
+ name = parse_events__term_type_str(term->type_term);
+ if (term->type_val == PARSE_EVENTS__TERM_TYPE_STR)
+ used += scnprintf(buf + used, sub_non_neg(len, used),
+ ",%s=%s", name,
+ term->val.str);
+ }
+ parse_events_terms__exit(&terms);
+ if (sub_non_neg(len, used) > 0) {
+ buf[used] = '/';
+ used++;
+ }
+ if (sub_non_neg(len, used) > 0) {
+ buf[used] = '\0';
+ used++;
+ } else
+ buf[len - 1] = '\0';
+
+ return buf;
+}
+
+static bool perf_pmu_alias__check_deprecated(struct perf_pmu *pmu, struct perf_pmu_alias *alias)
+{
+ struct perf_event_attr attr = {.config = 0,};
+ const char *check_terms;
+ bool has_legacy_config;
+
+ if (alias->legacy_deprecated_checked)
+ return alias->deprecated;
+
+ alias->legacy_deprecated_checked = true;
+ if (alias->deprecated)
+ return true;
+
+ check_terms = alias->terms;
+ has_legacy_config =
+ strstr(check_terms, "legacy-hardware-config=") != NULL ||
+ strstr(check_terms, "legacy-cache-config=") != NULL;
+ if (!has_legacy_config && alias->legacy_terms) {
+ check_terms = alias->legacy_terms;
+ has_legacy_config =
+ strstr(check_terms, "legacy-hardware-config=") != NULL ||
+ strstr(check_terms, "legacy-cache-config=") != NULL;
+ }
+ if (!has_legacy_config)
+ return false;
+
+ if (perf_pmu__parse_terms_to_attr(pmu, check_terms, &attr) != 0) {
+ /* Parsing failed, set as deprecated. */
+ alias->deprecated = true;
+ } else if (attr.type < PERF_TYPE_MAX) {
+ /* Flag unsupported legacy events as deprecated. */
+ alias->deprecated = !is_event_supported(attr.type, attr.config);
+ }
+ return alias->deprecated;
+}
+
+int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus,
+ void *state, pmu_event_callback cb)
+{
+ char buf[1024];
+ struct pmu_event_info info = {
+ .pmu = pmu,
+ .event_type_desc = "Kernel PMU event",
+ };
+ int ret = 0;
+ struct hashmap_entry *entry;
+ size_t bkt;
+ size_t size_rem;
+
+ if (perf_pmu__is_tracepoint(pmu))
+ return tp_pmu__for_each_event(pmu, state, cb);
+ if (perf_pmu__is_hwmon(pmu))
+ return hwmon_pmu__for_each_event(pmu, state, cb);
+ if (perf_pmu__is_drm(pmu))
+ return drm_pmu__for_each_event(pmu, state, cb);
+
+ pmu_aliases_parse(pmu);
+ pmu_add_cpu_aliases(pmu);
+ hashmap__for_each_entry(pmu->aliases, entry, bkt) {
+ struct perf_pmu_alias *event = entry->pvalue;
+ size_t buf_used, pmu_name_len;
+
+ if (perf_pmu__is_tool(pmu) && tool_pmu__skip_event(event->name))
+ continue;
+
+ info.pmu_name = event->pmu_name ?: pmu->name;
+ pmu_name_len = pmu_deduped_name_len(pmu, info.pmu_name,
+ skip_duplicate_pmus);
+ info.alias = NULL;
+ if (event->desc) {
+ info.name = event->name;
+ buf_used = 0;
+ } else {
+ info.name = format_alias(buf, sizeof(buf), pmu, event,
+ skip_duplicate_pmus);
+ if (pmu->is_core) {
+ info.alias = info.name;
+ info.name = event->name;
+ }
+ buf_used = strlen(buf) + 1;
+ }
+
+ info.scale_unit = NULL;
+ if (strlen(event->unit) || event->scale != 1.0) {
+ /* Check the remaining space */
+ size_rem = sub_non_neg(sizeof(buf), buf_used);
+
+ if (size_rem > 0) {
+ info.scale_unit = buf + buf_used;
+ buf_used += scnprintf(buf + buf_used, size_rem, "%G%s",
+ event->scale, event->unit) + 1;
+ }
+ }
+ info.desc = event->desc;
+ info.long_desc = event->long_desc;
+ info.encoding_desc = NULL;
+
+ /* Check the remaining space */
+ size_rem = sub_non_neg(sizeof(buf), buf_used);
+ if (size_rem > 0) {
+ info.encoding_desc = buf + buf_used;
+ buf_used += scnprintf(buf + buf_used, size_rem, "%.*s/%s/",
+ (int)pmu_name_len, info.pmu_name, event->terms) + 1;
+ }
+
+ info.str = event->terms;
+ info.topic = event->topic;
+ info.deprecated = perf_pmu_alias__check_deprecated(pmu, event);
+ ret = cb(state, &info);
+ if (ret)
+ goto out;
+ }
+ if (pmu->selectable) {
+ info.name = buf;
+ scnprintf(buf, sizeof(buf), "%s//", pmu->name);
+ info.alias = NULL;
+ info.scale_unit = NULL;
+ info.desc = NULL;
+ info.long_desc = NULL;
+ info.encoding_desc = NULL;
+ info.topic = NULL;
+ info.pmu_name = pmu->name;
+ info.deprecated = false;
+ ret = cb(state, &info);
+ }
+out:
+ return ret;
+}
+
+static bool perf_pmu___name_match(const struct perf_pmu *pmu, const char *to_match, bool wildcard)
+{
+ const char *names[2] = {
+ pmu->name,
+ pmu->alias_name,
+ };
+ if (pmu->is_core) {
+ for (size_t i = 0; i < ARRAY_SIZE(names); i++) {
+ const char *name = names[i];
+
+ if (!name)
+ continue;
+
+ if (!strcmp(name, to_match)) {
+ /* Exact name match. */
+ return true;
+ }
+ }
+ if (!strcmp(to_match, "default_core")) {
+ /*
+ * jevents and tests use default_core as a marker for any core
+ * PMU as the PMU name varies across architectures.
+ */
+ return true;
+ }
+ return false;
+ }
+ if (!pmu->is_uncore) {
+ /*
+ * PMU isn't core or uncore, some kind of broken CPU mask
+ * situation. Only match exact name.
+ */
+ for (size_t i = 0; i < ARRAY_SIZE(names); i++) {
+ const char *name = names[i];
+
+ if (!name)
+ continue;
+
+ if (!strcmp(name, to_match)) {
+ /* Exact name match. */
+ return true;
+ }
+ }
+ return false;
+ }
+ for (size_t i = 0; i < ARRAY_SIZE(names); i++) {
+ const char *name = names[i];
+
+ if (!name)
+ continue;
+
+ if (wildcard && perf_pmu__match_wildcard_uncore(name, to_match))
+ return true;
+ if (!wildcard && perf_pmu__match_ignoring_suffix_uncore(name, to_match))
+ return true;
+ }
+ return false;
+}
+
+/**
+ * perf_pmu__name_wildcard_match - Called by the jevents generated code to see
+ * if pmu matches the json to_match string.
+ * @pmu: The pmu whose name/alias to match.
+ * @to_match: The possible match to pmu_name.
+ */
+bool perf_pmu__name_wildcard_match(const struct perf_pmu *pmu, const char *to_match)
+{
+ return perf_pmu___name_match(pmu, to_match, /*wildcard=*/true);
+}
+
+/**
+ * perf_pmu__name_no_suffix_match - Does pmu's name match to_match ignoring any
+ * trailing suffix on the pmu_name and/or tok?
+ * @pmu: The pmu whose name/alias to match.
+ * @to_match: The possible match to pmu_name.
+ */
+bool perf_pmu__name_no_suffix_match(const struct perf_pmu *pmu, const char *to_match)
+{
+ return perf_pmu___name_match(pmu, to_match, /*wildcard=*/false);
+}
+
+bool perf_pmu__is_software(const struct perf_pmu *pmu)
+{
+ const char *known_sw_pmus[] = {
+ "kprobe",
+ "msr",
+ "uprobe",
+ };
+
+ if (pmu->is_core || pmu->is_uncore || pmu->auxtrace)
+ return false;
+ switch (pmu->type) {
+ case PERF_TYPE_HARDWARE: return false;
+ case PERF_TYPE_SOFTWARE: return true;
+ case PERF_TYPE_TRACEPOINT: return true;
+ case PERF_TYPE_HW_CACHE: return false;
+ case PERF_TYPE_RAW: return false;
+ case PERF_TYPE_BREAKPOINT: return true;
+ case PERF_PMU_TYPE_TOOL: return true;
+ default: break;
+ }
+ for (size_t i = 0; i < ARRAY_SIZE(known_sw_pmus); i++) {
+ if (!strcmp(pmu->name, known_sw_pmus[i]))
+ return true;
+ }
+ return false;
+}
+
+bool perf_pmu__benefits_from_affinity(struct perf_pmu *pmu)
+{
+ if (!pmu)
+ return true; /* Assume is core. */
+
+ /*
+ * All perf event PMUs should benefit from accessing the perf event
+ * contexts on the local CPU.
+ */
+ return pmu->type <= PERF_PMU_TYPE_PE_END;
+}
+
+FILE *perf_pmu__open_file(const struct perf_pmu *pmu, const char *name)
+{
+ char path[PATH_MAX];
+
+ if (!perf_pmu__pathname_scnprintf(path, sizeof(path), pmu->name, name) ||
+ !file_available(path))
+ return NULL;
+
+ return fopen(path, "r");
+}
+
+FILE *perf_pmu__open_file_at(const struct perf_pmu *pmu, int dirfd, const char *name)
+{
+ int fd;
+
+ fd = perf_pmu__pathname_fd(dirfd, pmu->name, name, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ return fdopen(fd, "r");
+}
+
+int perf_pmu__scan_file(const struct perf_pmu *pmu, const char *name, const char *fmt,
+ ...)
+{
+ va_list args;
+ FILE *file;
+ int ret = EOF;
+
+ va_start(args, fmt);
+ file = perf_pmu__open_file(pmu, name);
+ if (file) {
+ ret = vfscanf(file, fmt, args);
+ fclose(file);
+ }
+ va_end(args);
+ return ret;
+}
+
+int perf_pmu__scan_file_at(const struct perf_pmu *pmu, int dirfd, const char *name,
+ const char *fmt, ...)
+{
+ va_list args;
+ FILE *file;
+ int ret = EOF;
+
+ va_start(args, fmt);
+ file = perf_pmu__open_file_at(pmu, dirfd, name);
+ if (file) {
+ ret = vfscanf(file, fmt, args);
+ fclose(file);
+ }
+ va_end(args);
+ return ret;
+}
+
+bool perf_pmu__file_exists(const struct perf_pmu *pmu, const char *name)
+{
+ char path[PATH_MAX];
+
+ if (!perf_pmu__pathname_scnprintf(path, sizeof(path), pmu->name, name))
+ return false;
+
+ return file_available(path);
+}
+
+static int perf_pmu__new_caps(struct list_head *list, char *name, char *value)
+{
+ struct perf_pmu_caps *caps = zalloc(sizeof(*caps));
+
+ if (!caps)
+ return -ENOMEM;
+
+ caps->name = strdup(name);
+ if (!caps->name)
+ goto free_caps;
+ caps->value = strndup(value, strlen(value) - 1);
+ if (!caps->value)
+ goto free_name;
+ list_add_tail(&caps->list, list);
+ return 0;
+
+free_name:
+ zfree(&caps->name);
+free_caps:
+ free(caps);
+
+ return -ENOMEM;
+}
+
+static void perf_pmu__del_caps(struct perf_pmu *pmu)
+{
+ struct perf_pmu_caps *caps, *tmp;
+
+ list_for_each_entry_safe(caps, tmp, &pmu->caps, list) {
+ list_del(&caps->list);
+ zfree(&caps->name);
+ zfree(&caps->value);
+ free(caps);
+ }
+}
+
+struct perf_pmu_caps *perf_pmu__get_cap(struct perf_pmu *pmu, const char *name)
+{
+ struct perf_pmu_caps *caps;
+
+ list_for_each_entry(caps, &pmu->caps, list) {
+ if (!strcmp(caps->name, name))
+ return caps;
+ }
+ return NULL;
+}
+
+/*
+ * Reading/parsing the given pmu capabilities, which should be located at:
+ * /sys/bus/event_source/devices/<dev>/caps as sysfs group attributes.
+ * Return the number of capabilities
+ */
+int perf_pmu__caps_parse(struct perf_pmu *pmu)
+{
+ char caps_path[PATH_MAX];
+ struct io_dir caps_dir;
+ struct io_dirent64 *evt_ent;
+ int caps_fd;
+
+ if (pmu->caps_initialized)
+ return pmu->nr_caps;
+
+ pmu->nr_caps = 0;
+
+ if (!perf_pmu__pathname_scnprintf(caps_path, sizeof(caps_path), pmu->name, "caps"))
+ return -1;
+
+ caps_fd = open(caps_path, O_CLOEXEC | O_DIRECTORY | O_RDONLY);
+ if (caps_fd == -1) {
+ pmu->caps_initialized = true;
+ return 0; /* no error if caps does not exist */
+ }
+
+ io_dir__init(&caps_dir, caps_fd);
+
+ while ((evt_ent = io_dir__readdir(&caps_dir)) != NULL) {
+ char *name = evt_ent->d_name;
+ char value[128];
+ FILE *file;
+ int fd;
+
+ if (io_dir__is_dir(&caps_dir, evt_ent))
+ continue;
+
+ fd = openat(caps_fd, name, O_RDONLY);
+ if (fd == -1)
+ continue;
+ file = fdopen(fd, "r");
+ if (!file) {
+ close(fd);
+ continue;
+ }
+
+ if (!fgets(value, sizeof(value), file) ||
+ (perf_pmu__new_caps(&pmu->caps, name, value) < 0)) {
+ fclose(file);
+ continue;
+ }
+
+ pmu->nr_caps++;
+ fclose(file);
+ }
+
+ close(caps_fd);
+
+ pmu->caps_initialized = true;
+ return pmu->nr_caps;
+}
+
+static void perf_pmu__compute_config_masks(struct perf_pmu *pmu)
+{
+ struct perf_pmu_format *format;
+
+ if (pmu->config_masks_computed)
+ return;
+
+ list_for_each_entry(format, &pmu->format, list) {
+ unsigned int i;
+ __u64 *mask;
+
+ if (format->value >= PERF_PMU_FORMAT_VALUE_CONFIG_END)
+ continue;
+
+ pmu->config_masks_present = true;
+ mask = &pmu->config_masks[format->value];
+
+ for_each_set_bit(i, format->bits, PERF_PMU_FORMAT_BITS)
+ *mask |= 1ULL << i;
+ }
+ pmu->config_masks_computed = true;
+}
+
+void perf_pmu__warn_invalid_config(struct perf_pmu *pmu, __u64 config,
+ const char *name, int config_num,
+ const char *config_name)
+{
+ __u64 bits;
+ char buf[100];
+
+ perf_pmu__compute_config_masks(pmu);
+
+ /*
+ * Kernel doesn't export any valid format bits.
+ */
+ if (!pmu->config_masks_present)
+ return;
+
+ bits = config & ~pmu->config_masks[config_num];
+ if (bits == 0)
+ return;
+
+ bitmap_scnprintf((unsigned long *)&bits, sizeof(bits) * 8, buf, sizeof(buf));
+
+ pr_warning("WARNING: event '%s' not valid (bits %s of %s "
+ "'%llx' not supported by kernel)!\n",
+ name ?: "N/A", buf, config_name, config);
+}
+
+bool perf_pmu__wildcard_match(const struct perf_pmu *pmu, const char *wildcard_to_match)
+{
+ const char *names[2] = {
+ pmu->name,
+ pmu->alias_name,
+ };
+ bool need_fnmatch = strisglob(wildcard_to_match);
+
+ if (!strncmp(wildcard_to_match, "uncore_", 7))
+ wildcard_to_match += 7;
+
+ for (size_t i = 0; i < ARRAY_SIZE(names); i++) {
+ const char *pmu_name = names[i];
+
+ if (!pmu_name)
+ continue;
+
+ if (!strncmp(pmu_name, "uncore_", 7))
+ pmu_name += 7;
+
+ if (perf_pmu__match_wildcard(pmu_name, wildcard_to_match) ||
+ (need_fnmatch && !fnmatch(wildcard_to_match, pmu_name, 0)))
+ return true;
+ }
+ return false;
+}
+
+int perf_pmu__event_source_devices_scnprintf(char *pathname, size_t size)
+{
+ const char *sysfs = sysfs__mountpoint();
+
+ if (!sysfs)
+ return 0;
+ return scnprintf(pathname, size, "%s/bus/event_source/devices/", sysfs);
+}
+
+int perf_pmu__event_source_devices_fd(void)
+{
+ char path[PATH_MAX];
+ const char *sysfs = sysfs__mountpoint();
+
+ if (!sysfs)
+ return -1;
+
+ scnprintf(path, sizeof(path), "%s/bus/event_source/devices/", sysfs);
+ return open(path, O_DIRECTORY);
+}
+
+/*
+ * Fill 'buf' with the path to a file or folder in 'pmu_name' in
+ * sysfs. For example if pmu_name = "cs_etm" and 'filename' = "format"
+ * then pathname will be filled with
+ * "/sys/bus/event_source/devices/cs_etm/format"
+ *
+ * Return 0 if the sysfs mountpoint couldn't be found, if no characters were
+ * written or if the buffer size is exceeded.
+ */
+int perf_pmu__pathname_scnprintf(char *buf, size_t size,
+ const char *pmu_name, const char *filename)
+{
+ size_t len;
+
+ len = perf_pmu__event_source_devices_scnprintf(buf, size);
+ if (!len || (len + strlen(pmu_name) + strlen(filename) + 1) >= size)
+ return 0;
+
+ return scnprintf(buf + len, size - len, "%s/%s", pmu_name, filename);
+}
+
+int perf_pmu__pathname_fd(int dirfd, const char *pmu_name, const char *filename, int flags)
+{
+ char path[PATH_MAX];
+
+ scnprintf(path, sizeof(path), "%s/%s", pmu_name, filename);
+ return openat(dirfd, path, flags);
+}
+
+void perf_pmu__delete(struct perf_pmu *pmu)
+{
+ if (!pmu)
+ return;
+
+ if (perf_pmu__is_hwmon(pmu))
+ hwmon_pmu__exit(pmu);
+ else if (perf_pmu__is_drm(pmu))
+ drm_pmu__exit(pmu);
+
+ perf_pmu__del_formats(&pmu->format);
+ perf_pmu__del_aliases(pmu);
+ perf_pmu__del_caps(pmu);
+
+ perf_cpu_map__put(pmu->cpus);
+
+ zfree(&pmu->name);
+ zfree(&pmu->alias_name);
+ zfree(&pmu->id);
+ free(pmu);
+}
+
+const char *perf_pmu__name_from_config(struct perf_pmu *pmu, u64 config)
+{
+ struct hashmap_entry *entry;
+ size_t bkt;
+
+ if (!pmu)
+ return NULL;
+
+ pmu_aliases_parse(pmu);
+ pmu_add_cpu_aliases(pmu);
+ hashmap__for_each_entry(pmu->aliases, entry, bkt) {
+ struct perf_pmu_alias *event = entry->pvalue;
+ struct perf_event_attr attr = {.config = 0,};
+ int ret = perf_pmu__parse_terms_to_attr(pmu, event->terms, &attr);
+
+ if (ret == 0 && config == attr.config)
+ return event->name;
+ }
+ return NULL;
+}
+
+bool perf_pmu__reads_only_on_cpu_idx0(const struct perf_event_attr *attr)
+{
+ enum tool_pmu_event event;
+
+ if (attr->type != PERF_PMU_TYPE_TOOL)
+ return false;
+
+ event = (enum tool_pmu_event)attr->config;
+ return event != TOOL_PMU__EVENT_USER_TIME && event != TOOL_PMU__EVENT_SYSTEM_TIME;
+}