|  | // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) | 
|  | #include "drm_pmu.h" | 
|  | #include "counts.h" | 
|  | #include "cpumap.h" | 
|  | #include "debug.h" | 
|  | #include "evsel.h" | 
|  | #include "pmu.h" | 
|  | #include <perf/threadmap.h> | 
|  | #include <api/fs/fs.h> | 
|  | #include <api/io.h> | 
|  | #include <ctype.h> | 
|  | #include <dirent.h> | 
|  | #include <fcntl.h> | 
|  | #include <unistd.h> | 
|  | #include <linux/unistd.h> | 
|  | #include <linux/kcmp.h> | 
|  | #include <linux/zalloc.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/syscall.h> | 
|  | #include <sys/sysmacros.h> | 
|  | #include <sys/types.h> | 
|  |  | 
|  | enum drm_pmu_unit { | 
|  | DRM_PMU_UNIT_BYTES, | 
|  | DRM_PMU_UNIT_CAPACITY, | 
|  | DRM_PMU_UNIT_CYCLES, | 
|  | DRM_PMU_UNIT_HZ, | 
|  | DRM_PMU_UNIT_NS, | 
|  |  | 
|  | DRM_PMU_UNIT_MAX, | 
|  | }; | 
|  |  | 
|  | struct drm_pmu_event { | 
|  | const char *name; | 
|  | const char *desc; | 
|  | enum drm_pmu_unit unit; | 
|  | }; | 
|  |  | 
|  | struct drm_pmu { | 
|  | struct perf_pmu pmu; | 
|  | struct drm_pmu_event *events; | 
|  | int num_events; | 
|  | }; | 
|  |  | 
|  | static const char * const drm_pmu_unit_strs[DRM_PMU_UNIT_MAX] = { | 
|  | "bytes", | 
|  | "capacity", | 
|  | "cycles", | 
|  | "hz", | 
|  | "ns", | 
|  | }; | 
|  |  | 
|  | static const char * const drm_pmu_scale_unit_strs[DRM_PMU_UNIT_MAX] = { | 
|  | "1bytes", | 
|  | "1capacity", | 
|  | "1cycles", | 
|  | "1hz", | 
|  | "1ns", | 
|  | }; | 
|  |  | 
|  | bool perf_pmu__is_drm(const struct perf_pmu *pmu) | 
|  | { | 
|  | return pmu && pmu->type >= PERF_PMU_TYPE_DRM_START && | 
|  | pmu->type <= PERF_PMU_TYPE_DRM_END; | 
|  | } | 
|  |  | 
|  | bool evsel__is_drm(const struct evsel *evsel) | 
|  | { | 
|  | return perf_pmu__is_drm(evsel->pmu); | 
|  | } | 
|  |  | 
|  | static struct drm_pmu *add_drm_pmu(struct list_head *pmus, char *line, size_t line_len) | 
|  | { | 
|  | struct drm_pmu *drm; | 
|  | struct perf_pmu *pmu; | 
|  | const char *name; | 
|  | __u32 max_drm_pmu_type = 0, type; | 
|  | int i = 12; | 
|  |  | 
|  | if (line[line_len - 1] == '\n') | 
|  | line[line_len - 1] = '\0'; | 
|  | while (isspace(line[i])) | 
|  | i++; | 
|  |  | 
|  | line[--i] = '_'; | 
|  | line[--i] = 'm'; | 
|  | line[--i] = 'r'; | 
|  | line[--i] = 'd'; | 
|  | name = &line[i]; | 
|  |  | 
|  | list_for_each_entry(pmu, pmus, list) { | 
|  | if (!perf_pmu__is_drm(pmu)) | 
|  | continue; | 
|  | if (pmu->type > max_drm_pmu_type) | 
|  | max_drm_pmu_type = pmu->type; | 
|  | if (!strcmp(pmu->name, name)) { | 
|  | /* PMU already exists. */ | 
|  | return NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (max_drm_pmu_type != 0) | 
|  | type = max_drm_pmu_type + 1; | 
|  | else | 
|  | type = PERF_PMU_TYPE_DRM_START; | 
|  |  | 
|  | if (type > PERF_PMU_TYPE_DRM_END) { | 
|  | zfree(&drm); | 
|  | pr_err("Unable to encode DRM PMU type for %s\n", name); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | drm = zalloc(sizeof(*drm)); | 
|  | if (!drm) | 
|  | return NULL; | 
|  |  | 
|  | if (perf_pmu__init(&drm->pmu, type, name) != 0) { | 
|  | perf_pmu__delete(&drm->pmu); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | drm->pmu.cpus = perf_cpu_map__new("0"); | 
|  | if (!drm->pmu.cpus) { | 
|  | perf_pmu__delete(&drm->pmu); | 
|  | return NULL; | 
|  | } | 
|  | return drm; | 
|  | } | 
|  |  | 
|  |  | 
|  | static bool starts_with(const char *str, const char *prefix) | 
|  | { | 
|  | return !strncmp(prefix, str, strlen(prefix)); | 
|  | } | 
|  |  | 
|  | static int add_event(struct drm_pmu_event **events, int *num_events, | 
|  | const char *line, enum drm_pmu_unit unit, const char *desc) | 
|  | { | 
|  | const char *colon = strchr(line, ':'); | 
|  | struct drm_pmu_event *tmp; | 
|  |  | 
|  | if (!colon) | 
|  | return -EINVAL; | 
|  |  | 
|  | tmp = reallocarray(*events, *num_events + 1, sizeof(struct drm_pmu_event)); | 
|  | if (!tmp) | 
|  | return -ENOMEM; | 
|  | tmp[*num_events].unit = unit; | 
|  | tmp[*num_events].desc = desc; | 
|  | tmp[*num_events].name = strndup(line, colon - line); | 
|  | if (!tmp[*num_events].name) | 
|  | return -ENOMEM; | 
|  | (*num_events)++; | 
|  | *events = tmp; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int read_drm_pmus_cb(void *args, int fdinfo_dir_fd, const char *fd_name) | 
|  | { | 
|  | struct list_head *pmus = args; | 
|  | char buf[640]; | 
|  | struct io io; | 
|  | char *line = NULL; | 
|  | size_t line_len; | 
|  | struct drm_pmu *drm = NULL; | 
|  | struct drm_pmu_event *events = NULL; | 
|  | int num_events = 0; | 
|  |  | 
|  | io__init(&io, openat(fdinfo_dir_fd, fd_name, O_RDONLY), buf, sizeof(buf)); | 
|  | if (io.fd == -1) { | 
|  | /* Failed to open file, ignore. */ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | while (io__getline(&io, &line, &line_len) > 0) { | 
|  | if (starts_with(line, "drm-driver:")) { | 
|  | drm = add_drm_pmu(pmus, line, line_len); | 
|  | if (!drm) | 
|  | break; | 
|  | continue; | 
|  | } | 
|  | /* | 
|  | * Note the string matching below is alphabetical, with more | 
|  | * specific matches appearing before less specific. | 
|  | */ | 
|  | if (starts_with(line, "drm-active-")) { | 
|  | add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES, | 
|  | "Total memory active in one or more engines"); | 
|  | continue; | 
|  | } | 
|  | if (starts_with(line, "drm-cycles-")) { | 
|  | add_event(&events, &num_events, line, DRM_PMU_UNIT_CYCLES, | 
|  | "Busy cycles"); | 
|  | continue; | 
|  | } | 
|  | if (starts_with(line, "drm-engine-capacity-")) { | 
|  | add_event(&events, &num_events, line, DRM_PMU_UNIT_CAPACITY, | 
|  | "Engine capacity"); | 
|  | continue; | 
|  | } | 
|  | if (starts_with(line, "drm-engine-")) { | 
|  | add_event(&events, &num_events, line, DRM_PMU_UNIT_NS, | 
|  | "Utilization in ns"); | 
|  | continue; | 
|  | } | 
|  | if (starts_with(line, "drm-maxfreq-")) { | 
|  | add_event(&events, &num_events, line, DRM_PMU_UNIT_HZ, | 
|  | "Maximum frequency"); | 
|  | continue; | 
|  | } | 
|  | if (starts_with(line, "drm-purgeable-")) { | 
|  | add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES, | 
|  | "Size of resident and purgeable memory buffers"); | 
|  | continue; | 
|  | } | 
|  | if (starts_with(line, "drm-resident-")) { | 
|  | add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES, | 
|  | "Size of resident memory buffers"); | 
|  | continue; | 
|  | } | 
|  | if (starts_with(line, "drm-shared-")) { | 
|  | add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES, | 
|  | "Size of shared memory buffers"); | 
|  | continue; | 
|  | } | 
|  | if (starts_with(line, "drm-total-cycles-")) { | 
|  | add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES, | 
|  | "Total busy cycles"); | 
|  | continue; | 
|  | } | 
|  | if (starts_with(line, "drm-total-")) { | 
|  | add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES, | 
|  | "Size of shared and private memory"); | 
|  | continue; | 
|  | } | 
|  | if (verbose > 1 && starts_with(line, "drm-") && | 
|  | !starts_with(line, "drm-client-id:") && | 
|  | !starts_with(line, "drm-pdev:")) | 
|  | pr_debug("Unhandled DRM PMU fdinfo line match '%s'\n", line); | 
|  | } | 
|  | if (drm) { | 
|  | drm->events = events; | 
|  | drm->num_events = num_events; | 
|  | list_add_tail(&drm->pmu.list, pmus); | 
|  | } | 
|  | free(line); | 
|  | if (io.fd != -1) | 
|  | close(io.fd); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void drm_pmu__exit(struct perf_pmu *pmu) | 
|  | { | 
|  | struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu); | 
|  |  | 
|  | free(drm->events); | 
|  | } | 
|  |  | 
|  | bool drm_pmu__have_event(const struct perf_pmu *pmu, const char *name) | 
|  | { | 
|  | struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu); | 
|  |  | 
|  | if (!starts_with(name, "drm-")) | 
|  | return false; | 
|  |  | 
|  | for (int i = 0; i < drm->num_events; i++) { | 
|  | if (!strcasecmp(drm->events[i].name, name)) | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | int drm_pmu__for_each_event(const struct perf_pmu *pmu, void *state, pmu_event_callback cb) | 
|  | { | 
|  | struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu); | 
|  |  | 
|  | for (int i = 0; i < drm->num_events; i++) { | 
|  | char encoding_buf[128]; | 
|  | struct pmu_event_info info = { | 
|  | .pmu = pmu, | 
|  | .name = drm->events[i].name, | 
|  | .alias = NULL, | 
|  | .scale_unit = drm_pmu_scale_unit_strs[drm->events[i].unit], | 
|  | .desc = drm->events[i].desc, | 
|  | .long_desc = NULL, | 
|  | .encoding_desc = encoding_buf, | 
|  | .topic = "drm", | 
|  | .pmu_name = pmu->name, | 
|  | .event_type_desc = "DRM event", | 
|  | }; | 
|  | int ret; | 
|  |  | 
|  | snprintf(encoding_buf, sizeof(encoding_buf), "%s/config=0x%x/", pmu->name, i); | 
|  |  | 
|  | ret = cb(state, &info); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | size_t drm_pmu__num_events(const struct perf_pmu *pmu) | 
|  | { | 
|  | const struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu); | 
|  |  | 
|  | return drm->num_events; | 
|  | } | 
|  |  | 
|  | static int drm_pmu__index_for_event(const struct drm_pmu *drm, const char *name) | 
|  | { | 
|  | for (int i = 0; i < drm->num_events; i++) { | 
|  | if (!strcmp(drm->events[i].name, name)) | 
|  | return i; | 
|  | } | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static int drm_pmu__config_term(const struct drm_pmu *drm, | 
|  | struct perf_event_attr *attr, | 
|  | struct parse_events_term *term, | 
|  | struct parse_events_error *err) | 
|  | { | 
|  | if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) { | 
|  | int i = drm_pmu__index_for_event(drm, term->config); | 
|  |  | 
|  | if (i >= 0) { | 
|  | attr->config = i; | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | if (err) { | 
|  | char *err_str; | 
|  |  | 
|  | parse_events_error__handle(err, term->err_val, | 
|  | asprintf(&err_str, | 
|  | "unexpected drm event term (%s) %s", | 
|  | parse_events__term_type_str(term->type_term), | 
|  | term->config) < 0 | 
|  | ? strdup("unexpected drm event term") | 
|  | : err_str, | 
|  | NULL); | 
|  | } | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | int drm_pmu__config_terms(const struct perf_pmu *pmu, | 
|  | struct perf_event_attr *attr, | 
|  | struct parse_events_terms *terms, | 
|  | struct parse_events_error *err) | 
|  | { | 
|  | struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu); | 
|  | struct parse_events_term *term; | 
|  |  | 
|  | list_for_each_entry(term, &terms->terms, list) { | 
|  | if (drm_pmu__config_term(drm, attr, term, err)) | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int drm_pmu__check_alias(const struct perf_pmu *pmu, struct parse_events_terms *terms, | 
|  | struct perf_pmu_info *info, struct parse_events_error *err) | 
|  | { | 
|  | struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu); | 
|  | struct parse_events_term *term = | 
|  | list_first_entry(&terms->terms, struct parse_events_term, list); | 
|  |  | 
|  | if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) { | 
|  | int i = drm_pmu__index_for_event(drm, term->config); | 
|  |  | 
|  | if (i >= 0) { | 
|  | info->unit = drm_pmu_unit_strs[drm->events[i].unit]; | 
|  | info->scale = 1; | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | if (err) { | 
|  | char *err_str; | 
|  |  | 
|  | parse_events_error__handle(err, term->err_val, | 
|  | asprintf(&err_str, | 
|  | "unexpected drm event term (%s) %s", | 
|  | parse_events__term_type_str(term->type_term), | 
|  | term->config) < 0 | 
|  | ? strdup("unexpected drm event term") | 
|  | : err_str, | 
|  | NULL); | 
|  | } | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | struct minor_info { | 
|  | unsigned int *minors; | 
|  | int minors_num, minors_len; | 
|  | }; | 
|  |  | 
|  | static int for_each_drm_fdinfo_in_dir(int (*cb)(void *args, int fdinfo_dir_fd, const char *fd_name), | 
|  | void *args, int proc_dir, const char *pid_name, | 
|  | struct minor_info *minors) | 
|  | { | 
|  | char buf[256]; | 
|  | DIR *fd_dir; | 
|  | struct dirent *fd_entry; | 
|  | int fd_dir_fd, fdinfo_dir_fd = -1; | 
|  |  | 
|  |  | 
|  | scnprintf(buf, sizeof(buf), "%s/fd", pid_name); | 
|  | fd_dir_fd = openat(proc_dir, buf, O_DIRECTORY); | 
|  | if (fd_dir_fd == -1) | 
|  | return 0; /* Presumably lost race to open. */ | 
|  | fd_dir = fdopendir(fd_dir_fd); | 
|  | if (!fd_dir) { | 
|  | close(fd_dir_fd); | 
|  | return -ENOMEM; | 
|  | } | 
|  | while ((fd_entry = readdir(fd_dir)) != NULL) { | 
|  | struct stat stat; | 
|  | unsigned int minor; | 
|  | bool is_dup = false; | 
|  | int ret; | 
|  |  | 
|  | if (fd_entry->d_type != DT_LNK) | 
|  | continue; | 
|  |  | 
|  | if (fstatat(fd_dir_fd, fd_entry->d_name, &stat, 0) != 0) | 
|  | continue; | 
|  |  | 
|  | if ((stat.st_mode & S_IFMT) != S_IFCHR || major(stat.st_rdev) != 226) | 
|  | continue; | 
|  |  | 
|  | minor = minor(stat.st_rdev); | 
|  | for (int i = 0; i < minors->minors_num; i++) { | 
|  | if (minor(stat.st_rdev) == minors->minors[i]) { | 
|  | is_dup = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (is_dup) | 
|  | continue; | 
|  |  | 
|  | if (minors->minors_num == minors->minors_len) { | 
|  | unsigned int *tmp = reallocarray(minors->minors, minors->minors_len + 4, | 
|  | sizeof(unsigned int)); | 
|  |  | 
|  | if (tmp) { | 
|  | minors->minors = tmp; | 
|  | minors->minors_len += 4; | 
|  | } | 
|  | } | 
|  | minors->minors[minors->minors_num++] = minor; | 
|  | if (fdinfo_dir_fd == -1) { | 
|  | /* Open fdinfo dir if we have a DRM fd. */ | 
|  | scnprintf(buf, sizeof(buf), "%s/fdinfo", pid_name); | 
|  | fdinfo_dir_fd = openat(proc_dir, buf, O_DIRECTORY); | 
|  | if (fdinfo_dir_fd == -1) | 
|  | continue; | 
|  | } | 
|  | ret = cb(args, fdinfo_dir_fd, fd_entry->d_name); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  | if (fdinfo_dir_fd != -1) | 
|  | close(fdinfo_dir_fd); | 
|  | closedir(fd_dir); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int for_each_drm_fdinfo(bool skip_all_duplicates, | 
|  | int (*cb)(void *args, int fdinfo_dir_fd, const char *fd_name), | 
|  | void *args) | 
|  | { | 
|  | DIR *proc_dir; | 
|  | struct dirent *proc_entry; | 
|  | int ret; | 
|  | /* | 
|  | * minors maintains an array of DRM minor device numbers seen for a pid, | 
|  | * or for all pids if skip_all_duplicates is true, so that duplicates | 
|  | * are ignored. | 
|  | */ | 
|  | struct minor_info minors = { | 
|  | .minors = NULL, | 
|  | .minors_num = 0, | 
|  | .minors_len = 0, | 
|  | }; | 
|  |  | 
|  | proc_dir = opendir(procfs__mountpoint()); | 
|  | if (!proc_dir) | 
|  | return 0; | 
|  |  | 
|  | /* Walk through the /proc directory. */ | 
|  | while ((proc_entry = readdir(proc_dir)) != NULL) { | 
|  | if (proc_entry->d_type != DT_DIR || | 
|  | !isdigit(proc_entry->d_name[0])) | 
|  | continue; | 
|  | if (!skip_all_duplicates) { | 
|  | /* Reset the seen minor numbers for each pid. */ | 
|  | minors.minors_num = 0; | 
|  | } | 
|  | ret = for_each_drm_fdinfo_in_dir(cb, args, | 
|  | dirfd(proc_dir), proc_entry->d_name, | 
|  | &minors); | 
|  | if (ret) | 
|  | break; | 
|  | } | 
|  | free(minors.minors); | 
|  | closedir(proc_dir); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int perf_pmus__read_drm_pmus(struct list_head *pmus) | 
|  | { | 
|  | return for_each_drm_fdinfo(/*skip_all_duplicates=*/true, read_drm_pmus_cb, pmus); | 
|  | } | 
|  |  | 
|  | int evsel__drm_pmu_open(struct evsel *evsel, | 
|  | struct perf_thread_map *threads, | 
|  | int start_cpu_map_idx, int end_cpu_map_idx) | 
|  | { | 
|  | (void)evsel; | 
|  | (void)threads; | 
|  | (void)start_cpu_map_idx; | 
|  | (void)end_cpu_map_idx; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static uint64_t read_count_and_apply_unit(const char *count_and_unit, enum drm_pmu_unit unit) | 
|  | { | 
|  | char *unit_ptr = NULL; | 
|  | uint64_t count = strtoul(count_and_unit, &unit_ptr, 10); | 
|  |  | 
|  | if (!unit_ptr) | 
|  | return 0; | 
|  |  | 
|  | while (isblank(*unit_ptr)) | 
|  | unit_ptr++; | 
|  |  | 
|  | switch (unit) { | 
|  | case DRM_PMU_UNIT_BYTES: | 
|  | if (*unit_ptr == '\0') | 
|  | assert(count == 0); /* Generally undocumented, happens for 0. */ | 
|  | else if (!strcmp(unit_ptr, "KiB")) | 
|  | count *= 1024; | 
|  | else if (!strcmp(unit_ptr, "MiB")) | 
|  | count *= 1024 * 1024; | 
|  | else | 
|  | pr_err("Unexpected bytes unit '%s'\n", unit_ptr); | 
|  | break; | 
|  | case DRM_PMU_UNIT_CAPACITY: | 
|  | /* No units expected. */ | 
|  | break; | 
|  | case DRM_PMU_UNIT_CYCLES: | 
|  | /* No units expected. */ | 
|  | break; | 
|  | case DRM_PMU_UNIT_HZ: | 
|  | if (!strcmp(unit_ptr, "Hz")) | 
|  | count *= 1; | 
|  | else if (!strcmp(unit_ptr, "KHz")) | 
|  | count *= 1000; | 
|  | else if (!strcmp(unit_ptr, "MHz")) | 
|  | count *= 1000000; | 
|  | else | 
|  | pr_err("Unexpected hz unit '%s'\n", unit_ptr); | 
|  | break; | 
|  | case DRM_PMU_UNIT_NS: | 
|  | /* Only unit ns expected. */ | 
|  | break; | 
|  | case DRM_PMU_UNIT_MAX: | 
|  | default: | 
|  | break; | 
|  | } | 
|  | return count; | 
|  | } | 
|  |  | 
|  | static uint64_t read_drm_event(int fdinfo_dir_fd, const char *fd_name, | 
|  | const char *match, enum drm_pmu_unit unit) | 
|  | { | 
|  | char buf[640]; | 
|  | struct io io; | 
|  | char *line = NULL; | 
|  | size_t line_len; | 
|  | uint64_t count = 0; | 
|  |  | 
|  | io__init(&io, openat(fdinfo_dir_fd, fd_name, O_RDONLY), buf, sizeof(buf)); | 
|  | if (io.fd == -1) { | 
|  | /* Failed to open file, ignore. */ | 
|  | return 0; | 
|  | } | 
|  | while (io__getline(&io, &line, &line_len) > 0) { | 
|  | size_t i = strlen(match); | 
|  |  | 
|  | if (strncmp(line, match, i)) | 
|  | continue; | 
|  | if (line[i] != ':') | 
|  | continue; | 
|  | while (isblank(line[++i])) | 
|  | ; | 
|  | if (line[line_len - 1] == '\n') | 
|  | line[line_len - 1] = '\0'; | 
|  | count = read_count_and_apply_unit(&line[i], unit); | 
|  | break; | 
|  | } | 
|  | free(line); | 
|  | close(io.fd); | 
|  | return count; | 
|  | } | 
|  |  | 
|  | struct read_drm_event_cb_args { | 
|  | const char *match; | 
|  | uint64_t count; | 
|  | enum drm_pmu_unit unit; | 
|  | }; | 
|  |  | 
|  | static int read_drm_event_cb(void *vargs, int fdinfo_dir_fd, const char *fd_name) | 
|  | { | 
|  | struct read_drm_event_cb_args *args = vargs; | 
|  |  | 
|  | args->count += read_drm_event(fdinfo_dir_fd, fd_name, args->match, args->unit); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static uint64_t drm_pmu__read_system_wide(struct drm_pmu *drm, struct evsel *evsel) | 
|  | { | 
|  | struct read_drm_event_cb_args args = { | 
|  | .count = 0, | 
|  | .match = drm->events[evsel->core.attr.config].name, | 
|  | .unit = drm->events[evsel->core.attr.config].unit, | 
|  | }; | 
|  |  | 
|  | for_each_drm_fdinfo(/*skip_all_duplicates=*/false, read_drm_event_cb, &args); | 
|  | return args.count; | 
|  | } | 
|  |  | 
|  | static uint64_t drm_pmu__read_for_pid(struct drm_pmu *drm, struct evsel *evsel, int pid) | 
|  | { | 
|  | struct read_drm_event_cb_args args = { | 
|  | .count = 0, | 
|  | .match = drm->events[evsel->core.attr.config].name, | 
|  | .unit = drm->events[evsel->core.attr.config].unit, | 
|  | }; | 
|  | struct minor_info minors = { | 
|  | .minors = NULL, | 
|  | .minors_num = 0, | 
|  | .minors_len = 0, | 
|  | }; | 
|  | int proc_dir = open(procfs__mountpoint(), O_DIRECTORY); | 
|  | char pid_name[12]; | 
|  | int ret; | 
|  |  | 
|  | if (proc_dir < 0) | 
|  | return 0; | 
|  |  | 
|  | snprintf(pid_name, sizeof(pid_name), "%d", pid); | 
|  | ret = for_each_drm_fdinfo_in_dir(read_drm_event_cb, &args, proc_dir, pid_name, &minors); | 
|  | free(minors.minors); | 
|  | close(proc_dir); | 
|  | return ret == 0 ? args.count : 0; | 
|  | } | 
|  |  | 
|  | int evsel__drm_pmu_read(struct evsel *evsel, int cpu_map_idx, int thread) | 
|  | { | 
|  | struct drm_pmu *drm = container_of(evsel->pmu, struct drm_pmu, pmu); | 
|  | struct perf_counts_values *count, *old_count = NULL; | 
|  | int pid = perf_thread_map__pid(evsel->core.threads, thread); | 
|  | uint64_t counter; | 
|  |  | 
|  | if (pid != -1) | 
|  | counter = drm_pmu__read_for_pid(drm, evsel, pid); | 
|  | else | 
|  | counter = drm_pmu__read_system_wide(drm, evsel); | 
|  |  | 
|  | if (evsel->prev_raw_counts) | 
|  | old_count = perf_counts(evsel->prev_raw_counts, cpu_map_idx, thread); | 
|  |  | 
|  | count = perf_counts(evsel->counts, cpu_map_idx, thread); | 
|  | if (old_count) { | 
|  | count->val = old_count->val + counter; | 
|  | count->run = old_count->run + 1; | 
|  | count->ena = old_count->ena + 1; | 
|  | } else { | 
|  | count->val = counter; | 
|  | count->run++; | 
|  | count->ena++; | 
|  | } | 
|  | return 0; | 
|  | } |