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