xref: /linux/tools/perf/util/drm_pmu.c (revision 895306e3c881ae8a3227a31bf4e64865ad6a534f)
1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2 #include "drm_pmu.h"
3 #include "counts.h"
4 #include "cpumap.h"
5 #include "debug.h"
6 #include "evsel.h"
7 #include "pmu.h"
8 #include <perf/threadmap.h>
9 #include <api/fs/fs.h>
10 #include <api/io.h>
11 #include <ctype.h>
12 #include <dirent.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <unistd.h>
16 #include <linux/unistd.h>
17 #include <linux/kcmp.h>
18 #include <linux/string.h>
19 #include <linux/zalloc.h>
20 #include <sys/stat.h>
21 #include <sys/syscall.h>
22 #include <sys/sysmacros.h>
23 #include <sys/types.h>
24 
25 enum drm_pmu_unit {
26 	DRM_PMU_UNIT_BYTES,
27 	DRM_PMU_UNIT_CAPACITY,
28 	DRM_PMU_UNIT_CYCLES,
29 	DRM_PMU_UNIT_HZ,
30 	DRM_PMU_UNIT_NS,
31 
32 	DRM_PMU_UNIT_MAX,
33 };
34 
35 struct drm_pmu_event {
36 	const char *name;
37 	const char *desc;
38 	enum drm_pmu_unit unit;
39 };
40 
41 struct drm_pmu {
42 	struct perf_pmu pmu;
43 	struct drm_pmu_event *events;
44 	int num_events;
45 };
46 
47 static const char * const drm_pmu_unit_strs[DRM_PMU_UNIT_MAX] = {
48 	"bytes",
49 	"capacity",
50 	"cycles",
51 	"hz",
52 	"ns",
53 };
54 
55 static const char * const drm_pmu_scale_unit_strs[DRM_PMU_UNIT_MAX] = {
56 	"1bytes",
57 	"1capacity",
58 	"1cycles",
59 	"1hz",
60 	"1ns",
61 };
62 
63 bool perf_pmu__is_drm(const struct perf_pmu *pmu)
64 {
65 	return pmu && pmu->type >= PERF_PMU_TYPE_DRM_START &&
66 		pmu->type <= PERF_PMU_TYPE_DRM_END;
67 }
68 
69 bool evsel__is_drm(const struct evsel *evsel)
70 {
71 	return perf_pmu__is_drm(evsel->pmu);
72 }
73 
74 static struct drm_pmu *add_drm_pmu(struct list_head *pmus, char *line, size_t line_len)
75 {
76 	struct drm_pmu *drm;
77 	struct perf_pmu *pmu;
78 	const char *name;
79 	__u32 max_drm_pmu_type = 0, type;
80 	int i = 12;
81 
82 	if (line[line_len - 1] == '\n')
83 		line[line_len - 1] = '\0';
84 	while (isspace(line[i]))
85 		i++;
86 
87 	line[--i] = '_';
88 	line[--i] = 'm';
89 	line[--i] = 'r';
90 	line[--i] = 'd';
91 	name = &line[i];
92 
93 	list_for_each_entry(pmu, pmus, list) {
94 		if (!perf_pmu__is_drm(pmu))
95 			continue;
96 		if (pmu->type > max_drm_pmu_type)
97 			max_drm_pmu_type = pmu->type;
98 		if (!strcmp(pmu->name, name)) {
99 			/* PMU already exists. */
100 			return NULL;
101 		}
102 	}
103 
104 	if (max_drm_pmu_type != 0)
105 		type = max_drm_pmu_type + 1;
106 	else
107 		type = PERF_PMU_TYPE_DRM_START;
108 
109 	if (type > PERF_PMU_TYPE_DRM_END) {
110 		zfree(&drm);
111 		pr_err("Unable to encode DRM PMU type for %s\n", name);
112 		return NULL;
113 	}
114 
115 	drm = zalloc(sizeof(*drm));
116 	if (!drm)
117 		return NULL;
118 
119 	if (perf_pmu__init(&drm->pmu, type, name) != 0) {
120 		perf_pmu__delete(&drm->pmu);
121 		return NULL;
122 	}
123 
124 	drm->pmu.cpus = perf_cpu_map__new_int(0);
125 	if (!drm->pmu.cpus) {
126 		perf_pmu__delete(&drm->pmu);
127 		return NULL;
128 	}
129 	return drm;
130 }
131 
132 
133 static int add_event(struct drm_pmu_event **events, int *num_events,
134 		     const char *line, enum drm_pmu_unit unit, const char *desc)
135 {
136 	const char *colon = strchr(line, ':');
137 	struct drm_pmu_event *tmp;
138 
139 	if (!colon)
140 		return -EINVAL;
141 
142 	tmp = reallocarray(*events, *num_events + 1, sizeof(struct drm_pmu_event));
143 	if (!tmp)
144 		return -ENOMEM;
145 	tmp[*num_events].unit = unit;
146 	tmp[*num_events].desc = desc;
147 	tmp[*num_events].name = strndup(line, colon - line);
148 	if (!tmp[*num_events].name)
149 		return -ENOMEM;
150 	(*num_events)++;
151 	*events = tmp;
152 	return 0;
153 }
154 
155 static int read_drm_pmus_cb(void *args, int fdinfo_dir_fd, const char *fd_name)
156 {
157 	struct list_head *pmus = args;
158 	char buf[640];
159 	struct io io;
160 	char *line = NULL;
161 	size_t line_len;
162 	struct drm_pmu *drm = NULL;
163 	struct drm_pmu_event *events = NULL;
164 	int num_events = 0;
165 
166 	io__init(&io, openat(fdinfo_dir_fd, fd_name, O_RDONLY), buf, sizeof(buf));
167 	if (io.fd == -1) {
168 		/* Failed to open file, ignore. */
169 		return 0;
170 	}
171 
172 	while (io__getline(&io, &line, &line_len) > 0) {
173 		if (strstarts(line, "drm-driver:")) {
174 			drm = add_drm_pmu(pmus, line, line_len);
175 			if (!drm)
176 				break;
177 			continue;
178 		}
179 		/*
180 		 * Note the string matching below is alphabetical, with more
181 		 * specific matches appearing before less specific.
182 		 */
183 		if (strstarts(line, "drm-active-")) {
184 			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
185 				  "Total memory active in one or more engines");
186 			continue;
187 		}
188 		if (strstarts(line, "drm-cycles-")) {
189 			add_event(&events, &num_events, line, DRM_PMU_UNIT_CYCLES,
190 				"Busy cycles");
191 			continue;
192 		}
193 		if (strstarts(line, "drm-engine-capacity-")) {
194 			add_event(&events, &num_events, line, DRM_PMU_UNIT_CAPACITY,
195 				"Engine capacity");
196 			continue;
197 		}
198 		if (strstarts(line, "drm-engine-")) {
199 			add_event(&events, &num_events, line, DRM_PMU_UNIT_NS,
200 				  "Utilization in ns");
201 			continue;
202 		}
203 		if (strstarts(line, "drm-maxfreq-")) {
204 			add_event(&events, &num_events, line, DRM_PMU_UNIT_HZ,
205 				  "Maximum frequency");
206 			continue;
207 		}
208 		if (strstarts(line, "drm-purgeable-")) {
209 			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
210 				  "Size of resident and purgeable memory buffers");
211 			continue;
212 		}
213 		if (strstarts(line, "drm-resident-")) {
214 			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
215 				  "Size of resident memory buffers");
216 			continue;
217 		}
218 		if (strstarts(line, "drm-shared-")) {
219 			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
220 				  "Size of shared memory buffers");
221 			continue;
222 		}
223 		if (strstarts(line, "drm-total-cycles-")) {
224 			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
225 				  "Total busy cycles");
226 			continue;
227 		}
228 		if (strstarts(line, "drm-total-")) {
229 			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
230 				  "Size of shared and private memory");
231 			continue;
232 		}
233 		if (verbose > 1 && strstarts(line, "drm-") &&
234 		    !strstarts(line, "drm-client-id:") &&
235 		    !strstarts(line, "drm-pdev:"))
236 			pr_debug("Unhandled DRM PMU fdinfo line match '%s'\n", line);
237 	}
238 	if (drm) {
239 		drm->events = events;
240 		drm->num_events = num_events;
241 		list_add_tail(&drm->pmu.list, pmus);
242 	}
243 	free(line);
244 	if (io.fd != -1)
245 		close(io.fd);
246 	return 0;
247 }
248 
249 void drm_pmu__exit(struct perf_pmu *pmu)
250 {
251 	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
252 
253 	free(drm->events);
254 }
255 
256 bool drm_pmu__have_event(const struct perf_pmu *pmu, const char *name)
257 {
258 	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
259 
260 	if (!strstarts(name, "drm-"))
261 		return false;
262 
263 	for (int i = 0; i < drm->num_events; i++) {
264 		if (!strcasecmp(drm->events[i].name, name))
265 			return true;
266 	}
267 	return false;
268 }
269 
270 int drm_pmu__for_each_event(const struct perf_pmu *pmu, void *state, pmu_event_callback cb)
271 {
272 	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
273 
274 	for (int i = 0; i < drm->num_events; i++) {
275 		char encoding_buf[128];
276 		struct pmu_event_info info = {
277 			.pmu = pmu,
278 			.name = drm->events[i].name,
279 			.alias = NULL,
280 			.scale_unit = drm_pmu_scale_unit_strs[drm->events[i].unit],
281 			.desc = drm->events[i].desc,
282 			.long_desc = NULL,
283 			.encoding_desc = encoding_buf,
284 			.topic = "drm",
285 			.pmu_name = pmu->name,
286 			.event_type_desc = "DRM event",
287 		};
288 		int ret;
289 
290 		snprintf(encoding_buf, sizeof(encoding_buf), "%s/config=0x%x/", pmu->name, i);
291 
292 		ret = cb(state, &info);
293 		if (ret)
294 			return ret;
295 	}
296 	return 0;
297 }
298 
299 size_t drm_pmu__num_events(const struct perf_pmu *pmu)
300 {
301 	const struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
302 
303 	return drm->num_events;
304 }
305 
306 static int drm_pmu__index_for_event(const struct drm_pmu *drm, const char *name)
307 {
308 	for (int i = 0; i < drm->num_events; i++) {
309 		if (!strcmp(drm->events[i].name, name))
310 			return i;
311 	}
312 	return -1;
313 }
314 
315 static int drm_pmu__config_term(const struct drm_pmu *drm,
316 				  struct perf_event_attr *attr,
317 				  struct parse_events_term *term,
318 				  struct parse_events_error *err)
319 {
320 	if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) {
321 		int i = drm_pmu__index_for_event(drm, term->config);
322 
323 		if (i >= 0) {
324 			attr->config = i;
325 			return 0;
326 		}
327 	}
328 	if (err) {
329 		char *err_str;
330 
331 		parse_events_error__handle(err, term->err_val,
332 					asprintf(&err_str,
333 						"unexpected drm event term (%s) %s",
334 						parse_events__term_type_str(term->type_term),
335 						term->config) < 0
336 					? strdup("unexpected drm event term")
337 					: err_str,
338 					NULL);
339 	}
340 	return -EINVAL;
341 }
342 
343 int drm_pmu__config_terms(const struct perf_pmu *pmu,
344 			    struct perf_event_attr *attr,
345 			    struct parse_events_terms *terms,
346 			    struct parse_events_error *err)
347 {
348 	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
349 	struct parse_events_term *term;
350 
351 	list_for_each_entry(term, &terms->terms, list) {
352 		if (drm_pmu__config_term(drm, attr, term, err))
353 			return -EINVAL;
354 	}
355 
356 	return 0;
357 }
358 
359 int drm_pmu__check_alias(const struct perf_pmu *pmu, struct parse_events_terms *terms,
360 			 struct perf_pmu_info *info, struct parse_events_error *err)
361 {
362 	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
363 	struct parse_events_term *term =
364 		list_first_entry(&terms->terms, struct parse_events_term, list);
365 
366 	if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) {
367 		int i = drm_pmu__index_for_event(drm, term->config);
368 
369 		if (i >= 0) {
370 			info->unit = drm_pmu_unit_strs[drm->events[i].unit];
371 			info->scale = 1;
372 			return 0;
373 		}
374 	}
375 	if (err) {
376 		char *err_str;
377 
378 		parse_events_error__handle(err, term->err_val,
379 					asprintf(&err_str,
380 						"unexpected drm event term (%s) %s",
381 						parse_events__term_type_str(term->type_term),
382 						term->config) < 0
383 					? strdup("unexpected drm event term")
384 					: err_str,
385 					NULL);
386 	}
387 	return -EINVAL;
388 }
389 
390 struct minor_info {
391 	unsigned int *minors;
392 	int minors_num, minors_len;
393 };
394 
395 static int for_each_drm_fdinfo_in_dir(int (*cb)(void *args, int fdinfo_dir_fd, const char *fd_name),
396 				      void *args, int proc_dir, const char *pid_name,
397 				      struct minor_info *minors)
398 {
399 	char buf[256];
400 	DIR *fd_dir;
401 	struct dirent *fd_entry;
402 	int fd_dir_fd, fdinfo_dir_fd = -1;
403 
404 
405 	scnprintf(buf, sizeof(buf), "%s/fd", pid_name);
406 	fd_dir_fd = openat(proc_dir, buf, O_DIRECTORY);
407 	if (fd_dir_fd == -1)
408 		return 0; /* Presumably lost race to open. */
409 	fd_dir = fdopendir(fd_dir_fd);
410 	if (!fd_dir) {
411 		close(fd_dir_fd);
412 		return -ENOMEM;
413 	}
414 	while ((fd_entry = readdir(fd_dir)) != NULL) {
415 		struct stat stat;
416 		unsigned int minor;
417 		bool is_dup = false;
418 		int ret;
419 
420 		if (fd_entry->d_type != DT_LNK)
421 			continue;
422 
423 		if (fstatat(fd_dir_fd, fd_entry->d_name, &stat, 0) != 0)
424 			continue;
425 
426 		if ((stat.st_mode & S_IFMT) != S_IFCHR || major(stat.st_rdev) != 226)
427 			continue;
428 
429 		minor = minor(stat.st_rdev);
430 		for (int i = 0; i < minors->minors_num; i++) {
431 			if (minor(stat.st_rdev) == minors->minors[i]) {
432 				is_dup = true;
433 				break;
434 			}
435 		}
436 		if (is_dup)
437 			continue;
438 
439 		if (minors->minors_num == minors->minors_len) {
440 			unsigned int *tmp = reallocarray(minors->minors, minors->minors_len + 4,
441 							 sizeof(unsigned int));
442 
443 			if (tmp) {
444 				minors->minors = tmp;
445 				minors->minors_len += 4;
446 			}
447 		}
448 		minors->minors[minors->minors_num++] = minor;
449 		if (fdinfo_dir_fd == -1) {
450 			/* Open fdinfo dir if we have a DRM fd. */
451 			scnprintf(buf, sizeof(buf), "%s/fdinfo", pid_name);
452 			fdinfo_dir_fd = openat(proc_dir, buf, O_DIRECTORY);
453 			if (fdinfo_dir_fd == -1)
454 				continue;
455 		}
456 		ret = cb(args, fdinfo_dir_fd, fd_entry->d_name);
457 		if (ret)
458 			goto close_fdinfo;
459 	}
460 
461 close_fdinfo:
462 	if (fdinfo_dir_fd != -1)
463 		close(fdinfo_dir_fd);
464 	closedir(fd_dir);
465 	return 0;
466 }
467 
468 static int for_each_drm_fdinfo(bool skip_all_duplicates,
469 			       int (*cb)(void *args, int fdinfo_dir_fd, const char *fd_name),
470 			       void *args)
471 {
472 	DIR *proc_dir;
473 	struct dirent *proc_entry;
474 	int ret;
475 	/*
476 	 * minors maintains an array of DRM minor device numbers seen for a pid,
477 	 * or for all pids if skip_all_duplicates is true, so that duplicates
478 	 * are ignored.
479 	 */
480 	struct minor_info minors = {
481 		.minors = NULL,
482 		.minors_num = 0,
483 		.minors_len = 0,
484 	};
485 
486 	proc_dir = opendir(procfs__mountpoint());
487 	if (!proc_dir)
488 		return 0;
489 
490 	/* Walk through the /proc directory. */
491 	while ((proc_entry = readdir(proc_dir)) != NULL) {
492 		if (proc_entry->d_type != DT_DIR ||
493 		    !isdigit(proc_entry->d_name[0]))
494 			continue;
495 		if (!skip_all_duplicates) {
496 			/* Reset the seen minor numbers for each pid. */
497 			minors.minors_num = 0;
498 		}
499 		ret = for_each_drm_fdinfo_in_dir(cb, args,
500 						 dirfd(proc_dir), proc_entry->d_name,
501 						 &minors);
502 		if (ret)
503 			break;
504 	}
505 	free(minors.minors);
506 	closedir(proc_dir);
507 	return ret;
508 }
509 
510 int perf_pmus__read_drm_pmus(struct list_head *pmus)
511 {
512 	return for_each_drm_fdinfo(/*skip_all_duplicates=*/true, read_drm_pmus_cb, pmus);
513 }
514 
515 int evsel__drm_pmu_open(struct evsel *evsel,
516 			struct perf_thread_map *threads,
517 			int start_cpu_map_idx, int end_cpu_map_idx)
518 {
519 	(void)evsel;
520 	(void)threads;
521 	(void)start_cpu_map_idx;
522 	(void)end_cpu_map_idx;
523 	return 0;
524 }
525 
526 static uint64_t read_count_and_apply_unit(const char *count_and_unit, enum drm_pmu_unit unit)
527 {
528 	char *unit_ptr = NULL;
529 	uint64_t count = strtoul(count_and_unit, &unit_ptr, 10);
530 
531 	if (!unit_ptr)
532 		return 0;
533 
534 	while (isblank(*unit_ptr))
535 		unit_ptr++;
536 
537 	switch (unit) {
538 	case DRM_PMU_UNIT_BYTES:
539 		if (*unit_ptr == '\0')
540 			assert(count == 0); /* Generally undocumented, happens for 0. */
541 		else if (!strcmp(unit_ptr, "KiB"))
542 			count *= 1024;
543 		else if (!strcmp(unit_ptr, "MiB"))
544 			count *= 1024 * 1024;
545 		else
546 			pr_err("Unexpected bytes unit '%s'\n", unit_ptr);
547 		break;
548 	case DRM_PMU_UNIT_CAPACITY:
549 		/* No units expected. */
550 		break;
551 	case DRM_PMU_UNIT_CYCLES:
552 		/* No units expected. */
553 		break;
554 	case DRM_PMU_UNIT_HZ:
555 		if (!strcmp(unit_ptr, "Hz"))
556 			count *= 1;
557 		else if (!strcmp(unit_ptr, "KHz"))
558 			count *= 1000;
559 		else if (!strcmp(unit_ptr, "MHz"))
560 			count *= 1000000;
561 		else
562 			pr_err("Unexpected hz unit '%s'\n", unit_ptr);
563 		break;
564 	case DRM_PMU_UNIT_NS:
565 		/* Only unit ns expected. */
566 		break;
567 	case DRM_PMU_UNIT_MAX:
568 	default:
569 		break;
570 	}
571 	return count;
572 }
573 
574 static uint64_t read_drm_event(int fdinfo_dir_fd, const char *fd_name,
575 			       const char *match, enum drm_pmu_unit unit)
576 {
577 	char buf[640];
578 	struct io io;
579 	char *line = NULL;
580 	size_t line_len;
581 	uint64_t count = 0;
582 
583 	io__init(&io, openat(fdinfo_dir_fd, fd_name, O_RDONLY), buf, sizeof(buf));
584 	if (io.fd == -1) {
585 		/* Failed to open file, ignore. */
586 		return 0;
587 	}
588 	while (io__getline(&io, &line, &line_len) > 0) {
589 		size_t i = strlen(match);
590 
591 		if (strncmp(line, match, i))
592 			continue;
593 		if (line[i] != ':')
594 			continue;
595 		while (isblank(line[++i]))
596 			;
597 		if (line[line_len - 1] == '\n')
598 			line[line_len - 1] = '\0';
599 		count = read_count_and_apply_unit(&line[i], unit);
600 		break;
601 	}
602 	free(line);
603 	close(io.fd);
604 	return count;
605 }
606 
607 struct read_drm_event_cb_args {
608 	const char *match;
609 	uint64_t count;
610 	enum drm_pmu_unit unit;
611 };
612 
613 static int read_drm_event_cb(void *vargs, int fdinfo_dir_fd, const char *fd_name)
614 {
615 	struct read_drm_event_cb_args *args = vargs;
616 
617 	args->count += read_drm_event(fdinfo_dir_fd, fd_name, args->match, args->unit);
618 	return 0;
619 }
620 
621 static uint64_t drm_pmu__read_system_wide(struct drm_pmu *drm, struct evsel *evsel)
622 {
623 	struct read_drm_event_cb_args args = {
624 		.count = 0,
625 		.match = drm->events[evsel->core.attr.config].name,
626 		.unit = drm->events[evsel->core.attr.config].unit,
627 	};
628 
629 	for_each_drm_fdinfo(/*skip_all_duplicates=*/false, read_drm_event_cb, &args);
630 	return args.count;
631 }
632 
633 static uint64_t drm_pmu__read_for_pid(struct drm_pmu *drm, struct evsel *evsel, int pid)
634 {
635 	struct read_drm_event_cb_args args = {
636 		.count = 0,
637 		.match = drm->events[evsel->core.attr.config].name,
638 		.unit = drm->events[evsel->core.attr.config].unit,
639 	};
640 	struct minor_info minors = {
641 		.minors = NULL,
642 		.minors_num = 0,
643 		.minors_len = 0,
644 	};
645 	int proc_dir = open(procfs__mountpoint(), O_DIRECTORY);
646 	char pid_name[12];
647 	int ret;
648 
649 	if (proc_dir < 0)
650 		return 0;
651 
652 	snprintf(pid_name, sizeof(pid_name), "%d", pid);
653 	ret = for_each_drm_fdinfo_in_dir(read_drm_event_cb, &args, proc_dir, pid_name, &minors);
654 	free(minors.minors);
655 	close(proc_dir);
656 	return ret == 0 ? args.count : 0;
657 }
658 
659 int evsel__drm_pmu_read(struct evsel *evsel, int cpu_map_idx, int thread)
660 {
661 	struct drm_pmu *drm = container_of(evsel->pmu, struct drm_pmu, pmu);
662 	struct perf_counts_values *count, *old_count = NULL;
663 	int pid = perf_thread_map__pid(evsel->core.threads, thread);
664 	uint64_t counter;
665 
666 	if (pid != -1)
667 		counter = drm_pmu__read_for_pid(drm, evsel, pid);
668 	else
669 		counter = drm_pmu__read_system_wide(drm, evsel);
670 
671 	if (evsel->prev_raw_counts)
672 		old_count = perf_counts(evsel->prev_raw_counts, cpu_map_idx, thread);
673 
674 	count = perf_counts(evsel->counts, cpu_map_idx, thread);
675 	if (old_count) {
676 		count->val = old_count->val + counter;
677 		count->run = old_count->run + 1;
678 		count->ena = old_count->ena + 1;
679 	} else {
680 		count->val = counter;
681 		count->run++;
682 		count->ena++;
683 	}
684 	return 0;
685 }
686