| // SPDX-License-Identifier: GPL-2.0 |
| #include <string.h> |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <linux/stddef.h> |
| #include <linux/perf_event.h> |
| #include <linux/zalloc.h> |
| #include <api/fs/fs.h> |
| #include <errno.h> |
| |
| #include "../../../util/intel-pt.h" |
| #include "../../../util/intel-bts.h" |
| #include "../../../util/pmu.h" |
| #include "../../../util/fncache.h" |
| #include "../../../util/pmus.h" |
| #include "env.h" |
| |
| struct pmu_alias { |
| char *name; |
| char *alias; |
| struct list_head list; |
| }; |
| |
| static LIST_HEAD(pmu_alias_name_list); |
| static bool cached_list; |
| |
| struct perf_event_attr *perf_pmu__get_default_config(struct perf_pmu *pmu __maybe_unused) |
| { |
| #ifdef HAVE_AUXTRACE_SUPPORT |
| if (!strcmp(pmu->name, INTEL_PT_PMU_NAME)) { |
| pmu->auxtrace = true; |
| return intel_pt_pmu_default_config(pmu); |
| } |
| if (!strcmp(pmu->name, INTEL_BTS_PMU_NAME)) { |
| pmu->auxtrace = true; |
| pmu->selectable = true; |
| } |
| #endif |
| return NULL; |
| } |
| |
| static void pmu_alias__delete(struct pmu_alias *pmu_alias) |
| { |
| if (!pmu_alias) |
| return; |
| |
| zfree(&pmu_alias->name); |
| zfree(&pmu_alias->alias); |
| free(pmu_alias); |
| } |
| |
| static struct pmu_alias *pmu_alias__new(char *name, char *alias) |
| { |
| struct pmu_alias *pmu_alias = zalloc(sizeof(*pmu_alias)); |
| |
| if (pmu_alias) { |
| pmu_alias->name = strdup(name); |
| if (!pmu_alias->name) |
| goto out_delete; |
| |
| pmu_alias->alias = strdup(alias); |
| if (!pmu_alias->alias) |
| goto out_delete; |
| } |
| return pmu_alias; |
| |
| out_delete: |
| pmu_alias__delete(pmu_alias); |
| return NULL; |
| } |
| |
| static int setup_pmu_alias_list(void) |
| { |
| int fd, dirfd; |
| DIR *dir; |
| struct dirent *dent; |
| struct pmu_alias *pmu_alias; |
| char buf[MAX_PMU_NAME_LEN]; |
| FILE *file; |
| int ret = -ENOMEM; |
| |
| dirfd = perf_pmu__event_source_devices_fd(); |
| if (dirfd < 0) |
| return -1; |
| |
| dir = fdopendir(dirfd); |
| if (!dir) |
| return -errno; |
| |
| while ((dent = readdir(dir))) { |
| if (!strcmp(dent->d_name, ".") || |
| !strcmp(dent->d_name, "..")) |
| continue; |
| |
| fd = perf_pmu__pathname_fd(dirfd, dent->d_name, "alias", O_RDONLY); |
| if (fd < 0) |
| continue; |
| |
| file = fdopen(fd, "r"); |
| if (!file) |
| continue; |
| |
| if (!fgets(buf, sizeof(buf), file)) { |
| fclose(file); |
| continue; |
| } |
| |
| fclose(file); |
| |
| /* Remove the last '\n' */ |
| buf[strlen(buf) - 1] = 0; |
| |
| pmu_alias = pmu_alias__new(dent->d_name, buf); |
| if (!pmu_alias) |
| goto close_dir; |
| |
| list_add_tail(&pmu_alias->list, &pmu_alias_name_list); |
| } |
| |
| ret = 0; |
| |
| close_dir: |
| closedir(dir); |
| return ret; |
| } |
| |
| static const char *__pmu_find_real_name(const char *name) |
| { |
| struct pmu_alias *pmu_alias; |
| |
| list_for_each_entry(pmu_alias, &pmu_alias_name_list, list) { |
| if (!strcmp(name, pmu_alias->alias)) |
| return pmu_alias->name; |
| } |
| |
| return name; |
| } |
| |
| const char *pmu_find_real_name(const char *name) |
| { |
| if (cached_list) |
| return __pmu_find_real_name(name); |
| |
| setup_pmu_alias_list(); |
| cached_list = true; |
| |
| return __pmu_find_real_name(name); |
| } |
| |
| static const char *__pmu_find_alias_name(const char *name) |
| { |
| struct pmu_alias *pmu_alias; |
| |
| list_for_each_entry(pmu_alias, &pmu_alias_name_list, list) { |
| if (!strcmp(name, pmu_alias->name)) |
| return pmu_alias->alias; |
| } |
| return NULL; |
| } |
| |
| const char *pmu_find_alias_name(const char *name) |
| { |
| if (cached_list) |
| return __pmu_find_alias_name(name); |
| |
| setup_pmu_alias_list(); |
| cached_list = true; |
| |
| return __pmu_find_alias_name(name); |
| } |
| |
| int perf_pmus__num_mem_pmus(void) |
| { |
| /* AMD uses IBS OP pmu and not a core PMU for perf mem/c2c */ |
| if (x86__is_amd_cpu()) |
| return 1; |
| |
| /* Intel uses core pmus for perf mem/c2c */ |
| return perf_pmus__num_core_pmus(); |
| } |