diff options
Diffstat (limited to 'tools/perf/util/pmu.c')
| -rw-r--r-- | tools/perf/util/pmu.c | 2790 |
1 files changed, 2790 insertions, 0 deletions
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c new file mode 100644 index 000000000..836e3b561 --- /dev/null +++ b/tools/perf/util/pmu.c @@ -0,0 +1,2790 @@ +// 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; + + if (pmu->is_core && !strcmp(wildcard_to_match, "default_core")) + return true; + + 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; +} |
