// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include 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; }