1 /*
2 * Copyright (C) 2016 Rob Clark <robclark@freedesktop.org>
3 * All Rights Reserved.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 * OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25 #include <assert.h>
26 #include <curses.h>
27 #include <err.h>
28 #include <inttypes.h>
29 #include <libconfig.h>
30 #include <locale.h>
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <xf86drm.h>
37
38 #include "drm/freedreno_drmif.h"
39 #include "drm/freedreno_ringbuffer.h"
40
41 #include "util/os_file.h"
42
43 #include "freedreno_dt.h"
44 #include "freedreno_perfcntr.h"
45
46 #define MAX_CNTR_PER_GROUP 24
47
48 /* NOTE first counter group should always be CP, since we unconditionally
49 * use CP counter to measure the gpu freq.
50 */
51
52 struct counter_group {
53 const struct fd_perfcntr_group *group;
54
55 struct {
56 const struct fd_perfcntr_counter *counter;
57 uint16_t select_val;
58 volatile uint32_t *val_hi;
59 volatile uint32_t *val_lo;
60 } counter[MAX_CNTR_PER_GROUP];
61
62 /* last sample time: */
63 uint32_t stime[MAX_CNTR_PER_GROUP];
64 /* for now just care about the low 32b value.. at least then we don't
65 * have to really care that we can't sample both hi and lo regs at the
66 * same time:
67 */
68 uint32_t last[MAX_CNTR_PER_GROUP];
69 /* current value, ie. by how many did the counter increase in last
70 * sampling period divided by the sampling period:
71 */
72 float current[MAX_CNTR_PER_GROUP];
73 /* name of currently selected counters (for UI): */
74 const char *label[MAX_CNTR_PER_GROUP];
75 };
76
77 static struct {
78 void *io;
79 uint32_t chipid;
80 uint32_t min_freq;
81 uint32_t max_freq;
82 /* per-generation table of counters: */
83 unsigned ngroups;
84 struct counter_group *groups;
85 /* drm device (for writing select regs via ring): */
86 struct fd_device *dev;
87 struct fd_pipe *pipe;
88 struct fd_submit *submit;
89 struct fd_ringbuffer *ring;
90 } dev;
91
92 static void config_save(void);
93 static void config_restore(void);
94 static void restore_counter_groups(void);
95
96 /*
97 * helpers
98 */
99
100 static uint32_t
gettime_us(void)101 gettime_us(void)
102 {
103 struct timespec ts;
104 clock_gettime(CLOCK_MONOTONIC, &ts);
105 return (ts.tv_sec * 1000000) + (ts.tv_nsec / 1000);
106 }
107
108 static uint32_t
delta(uint32_t a,uint32_t b)109 delta(uint32_t a, uint32_t b)
110 {
111 /* deal with rollover: */
112 if (a > b)
113 return 0xffffffff - a + b;
114 else
115 return b - a;
116 }
117
118 static void
find_device(void)119 find_device(void)
120 {
121 int ret, fd;
122
123 fd = drmOpenWithType("msm", NULL, DRM_NODE_RENDER);
124 if (fd < 0)
125 err(1, "could not open drm device");
126
127 dev.dev = fd_device_new(fd);
128 dev.pipe = fd_pipe_new(dev.dev, FD_PIPE_3D);
129
130 uint64_t val;
131 ret = fd_pipe_get_param(dev.pipe, FD_CHIP_ID, &val);
132 if (ret) {
133 err(1, "could not get gpu-id");
134 }
135 dev.chipid = val;
136
137 #define CHIP_FMT "d%d%d.%d"
138 #define CHIP_ARGS(chipid) \
139 ((chipid) >> 24) & 0xff, ((chipid) >> 16) & 0xff, ((chipid) >> 8) & 0xff, \
140 ((chipid) >> 0) & 0xff
141 printf("device: a%" CHIP_FMT "\n", CHIP_ARGS(dev.chipid));
142
143 /* try MAX_FREQ first as that will work regardless of old dt
144 * dt bindings vs upstream bindings:
145 */
146 ret = fd_pipe_get_param(dev.pipe, FD_MAX_FREQ, &val);
147 if (ret) {
148 printf("falling back to parsing DT bindings for freq\n");
149 if (!fd_dt_find_freqs(&dev.min_freq, &dev.max_freq))
150 err(1, "could not find GPU freqs");
151 } else {
152 dev.min_freq = 0;
153 dev.max_freq = val;
154 }
155
156 printf("min_freq=%u, max_freq=%u\n", dev.min_freq, dev.max_freq);
157
158 dev.io = fd_dt_find_io();
159 if (!dev.io) {
160 err(1, "could not map device");
161 }
162 }
163
164 /*
165 * perf-monitor
166 */
167
168 static void
flush_ring(void)169 flush_ring(void)
170 {
171 int ret;
172
173 if (!dev.submit)
174 return;
175
176 struct fd_submit_fence fence = {};
177 util_queue_fence_init(&fence.ready);
178
179 ret = fd_submit_flush(dev.submit, -1, &fence);
180
181 if (ret)
182 errx(1, "submit failed: %d", ret);
183 util_queue_fence_wait(&fence.ready);
184 fd_ringbuffer_del(dev.ring);
185 fd_submit_del(dev.submit);
186
187 dev.ring = NULL;
188 dev.submit = NULL;
189 }
190
191 static void
select_counter(struct counter_group * group,int ctr,int n)192 select_counter(struct counter_group *group, int ctr, int n)
193 {
194 assert(n < group->group->num_countables);
195 assert(ctr < group->group->num_counters);
196
197 group->label[ctr] = group->group->countables[n].name;
198 group->counter[ctr].select_val = n;
199
200 if (!dev.submit) {
201 dev.submit = fd_submit_new(dev.pipe);
202 dev.ring = fd_submit_new_ringbuffer(
203 dev.submit, 0x1000, FD_RINGBUFFER_PRIMARY | FD_RINGBUFFER_GROWABLE);
204 }
205
206 /* bashing select register directly while gpu is active will end
207 * in tears.. so we need to write it via the ring:
208 *
209 * TODO it would help startup time, if gpu is loaded, to batch
210 * all the initial writes and do a single flush.. although that
211 * makes things more complicated for capturing inital sample value
212 */
213 struct fd_ringbuffer *ring = dev.ring;
214 switch (dev.chipid >> 24) {
215 case 2:
216 case 3:
217 case 4:
218 OUT_PKT3(ring, CP_WAIT_FOR_IDLE, 1);
219 OUT_RING(ring, 0x00000000);
220
221 if (group->group->counters[ctr].enable) {
222 OUT_PKT0(ring, group->group->counters[ctr].enable, 1);
223 OUT_RING(ring, 0);
224 }
225
226 if (group->group->counters[ctr].clear) {
227 OUT_PKT0(ring, group->group->counters[ctr].clear, 1);
228 OUT_RING(ring, 1);
229
230 OUT_PKT0(ring, group->group->counters[ctr].clear, 1);
231 OUT_RING(ring, 0);
232 }
233
234 OUT_PKT0(ring, group->group->counters[ctr].select_reg, 1);
235 OUT_RING(ring, n);
236
237 if (group->group->counters[ctr].enable) {
238 OUT_PKT0(ring, group->group->counters[ctr].enable, 1);
239 OUT_RING(ring, 1);
240 }
241
242 break;
243 case 5:
244 case 6:
245 OUT_PKT7(ring, CP_WAIT_FOR_IDLE, 0);
246
247 if (group->group->counters[ctr].enable) {
248 OUT_PKT4(ring, group->group->counters[ctr].enable, 1);
249 OUT_RING(ring, 0);
250 }
251
252 if (group->group->counters[ctr].clear) {
253 OUT_PKT4(ring, group->group->counters[ctr].clear, 1);
254 OUT_RING(ring, 1);
255
256 OUT_PKT4(ring, group->group->counters[ctr].clear, 1);
257 OUT_RING(ring, 0);
258 }
259
260 OUT_PKT4(ring, group->group->counters[ctr].select_reg, 1);
261 OUT_RING(ring, n);
262
263 if (group->group->counters[ctr].enable) {
264 OUT_PKT4(ring, group->group->counters[ctr].enable, 1);
265 OUT_RING(ring, 1);
266 }
267
268 break;
269 }
270
271 group->last[ctr] = *group->counter[ctr].val_lo;
272 group->stime[ctr] = gettime_us();
273 }
274
275 static void
resample_counter(struct counter_group * group,int ctr)276 resample_counter(struct counter_group *group, int ctr)
277 {
278 uint32_t val = *group->counter[ctr].val_lo;
279 uint32_t t = gettime_us();
280 uint32_t dt = delta(group->stime[ctr], t);
281 uint32_t dval = delta(group->last[ctr], val);
282 group->current[ctr] = (float)dval * 1000000.0 / (float)dt;
283 group->last[ctr] = val;
284 group->stime[ctr] = t;
285 }
286
287 #define REFRESH_MS 500
288
289 /* sample all the counters: */
290 static void
resample(void)291 resample(void)
292 {
293 static uint64_t last_time;
294 uint64_t current_time = gettime_us();
295
296 if ((current_time - last_time) < (REFRESH_MS * 1000 / 2))
297 return;
298
299 last_time = current_time;
300
301 for (unsigned i = 0; i < dev.ngroups; i++) {
302 struct counter_group *group = &dev.groups[i];
303 for (unsigned j = 0; j < group->group->num_counters; j++) {
304 resample_counter(group, j);
305 }
306 }
307 }
308
309 /*
310 * The UI
311 */
312
313 #define COLOR_GROUP_HEADER 1
314 #define COLOR_FOOTER 2
315 #define COLOR_INVERSE 3
316
317 static int w, h;
318 static int ctr_width;
319 static int max_rows, current_cntr = 1;
320
321 static void
redraw_footer(WINDOW * win)322 redraw_footer(WINDOW *win)
323 {
324 char *footer;
325 int n;
326
327 n = asprintf(&footer, " fdperf: a%" CHIP_FMT " (%.2fMHz..%.2fMHz)",
328 CHIP_ARGS(dev.chipid), ((float)dev.min_freq) / 1000000.0,
329 ((float)dev.max_freq) / 1000000.0);
330
331 wmove(win, h - 1, 0);
332 wattron(win, COLOR_PAIR(COLOR_FOOTER));
333 waddstr(win, footer);
334 whline(win, ' ', w - n);
335 wattroff(win, COLOR_PAIR(COLOR_FOOTER));
336
337 free(footer);
338 }
339
340 static void
redraw_group_header(WINDOW * win,int row,const char * name)341 redraw_group_header(WINDOW *win, int row, const char *name)
342 {
343 wmove(win, row, 0);
344 wattron(win, A_BOLD);
345 wattron(win, COLOR_PAIR(COLOR_GROUP_HEADER));
346 waddstr(win, name);
347 whline(win, ' ', w - strlen(name));
348 wattroff(win, COLOR_PAIR(COLOR_GROUP_HEADER));
349 wattroff(win, A_BOLD);
350 }
351
352 static void
redraw_counter_label(WINDOW * win,int row,const char * name,bool selected)353 redraw_counter_label(WINDOW *win, int row, const char *name, bool selected)
354 {
355 int n = strlen(name);
356 assert(n <= ctr_width);
357 wmove(win, row, 0);
358 whline(win, ' ', ctr_width - n);
359 wmove(win, row, ctr_width - n);
360 if (selected)
361 wattron(win, COLOR_PAIR(COLOR_INVERSE));
362 waddstr(win, name);
363 if (selected)
364 wattroff(win, COLOR_PAIR(COLOR_INVERSE));
365 waddstr(win, ": ");
366 }
367
368 static void
redraw_counter_value_cycles(WINDOW * win,float val)369 redraw_counter_value_cycles(WINDOW *win, float val)
370 {
371 char *str;
372 int x = getcurx(win);
373 int valwidth = w - x;
374 int barwidth, n;
375
376 /* convert to fraction of max freq: */
377 val = val / (float)dev.max_freq;
378
379 /* figure out percentage-bar width: */
380 barwidth = (int)(val * valwidth);
381
382 /* sometimes things go over 100%.. idk why, could be
383 * things running faster than base clock, or counter
384 * summing up cycles in multiple cores?
385 */
386 barwidth = MIN2(barwidth, valwidth - 1);
387
388 n = asprintf(&str, "%.2f%%", 100.0 * val);
389 wattron(win, COLOR_PAIR(COLOR_INVERSE));
390 waddnstr(win, str, barwidth);
391 if (barwidth > n) {
392 whline(win, ' ', barwidth - n);
393 wmove(win, getcury(win), x + barwidth);
394 }
395 wattroff(win, COLOR_PAIR(COLOR_INVERSE));
396 if (barwidth < n)
397 waddstr(win, str + barwidth);
398 whline(win, ' ', w - getcurx(win));
399
400 free(str);
401 }
402
403 static void
redraw_counter_value_raw(WINDOW * win,float val)404 redraw_counter_value_raw(WINDOW *win, float val)
405 {
406 char *str;
407 (void)asprintf(&str, "%'.2f", val);
408 waddstr(win, str);
409 whline(win, ' ', w - getcurx(win));
410 free(str);
411 }
412
413 static void
redraw_counter(WINDOW * win,int row,struct counter_group * group,int ctr,bool selected)414 redraw_counter(WINDOW *win, int row, struct counter_group *group, int ctr,
415 bool selected)
416 {
417 redraw_counter_label(win, row, group->label[ctr], selected);
418
419 /* quick hack, if the label has "CYCLE" in the name, it is
420 * probably a cycle counter ;-)
421 * Perhaps add more info in rnndb schema to know how to
422 * treat individual counters (ie. which are cycles, and
423 * for those we want to present as a percentage do we
424 * need to scale the result.. ie. is it running at some
425 * multiple or divisor of core clk, etc)
426 *
427 * TODO it would be much more clever to get this from xml
428 * Also.. in some cases I think we want to know how many
429 * units the counter is counting for, ie. if a320 has 2x
430 * shader as a306 we might need to scale the result..
431 */
432 if (strstr(group->label[ctr], "CYCLE") ||
433 strstr(group->label[ctr], "BUSY") || strstr(group->label[ctr], "IDLE"))
434 redraw_counter_value_cycles(win, group->current[ctr]);
435 else
436 redraw_counter_value_raw(win, group->current[ctr]);
437 }
438
439 static void
redraw(WINDOW * win)440 redraw(WINDOW *win)
441 {
442 static int scroll = 0;
443 int max, row = 0;
444
445 w = getmaxx(win);
446 h = getmaxy(win);
447
448 max = h - 3;
449
450 if ((current_cntr - scroll) > (max - 1)) {
451 scroll = current_cntr - (max - 1);
452 } else if ((current_cntr - 1) < scroll) {
453 scroll = current_cntr - 1;
454 }
455
456 for (unsigned i = 0; i < dev.ngroups; i++) {
457 struct counter_group *group = &dev.groups[i];
458 unsigned j = 0;
459
460 /* NOTE skip CP the first CP counter */
461 if (i == 0)
462 j++;
463
464 if (j < group->group->num_counters) {
465 if ((scroll <= row) && ((row - scroll) < max))
466 redraw_group_header(win, row - scroll, group->group->name);
467 row++;
468 }
469
470 for (; j < group->group->num_counters; j++) {
471 if ((scroll <= row) && ((row - scroll) < max))
472 redraw_counter(win, row - scroll, group, j, row == current_cntr);
473 row++;
474 }
475 }
476
477 /* convert back to physical (unscrolled) offset: */
478 row = max;
479
480 redraw_group_header(win, row, "Status");
481 row++;
482
483 /* Draw GPU freq row: */
484 redraw_counter_label(win, row, "Freq (MHz)", false);
485 redraw_counter_value_raw(win, dev.groups[0].current[0] / 1000000.0);
486 row++;
487
488 redraw_footer(win);
489
490 refresh();
491 }
492
493 static struct counter_group *
current_counter(int * ctr)494 current_counter(int *ctr)
495 {
496 int n = 0;
497
498 for (unsigned i = 0; i < dev.ngroups; i++) {
499 struct counter_group *group = &dev.groups[i];
500 unsigned j = 0;
501
502 /* NOTE skip the first CP counter (CP_ALWAYS_COUNT) */
503 if (i == 0)
504 j++;
505
506 /* account for group header: */
507 if (j < group->group->num_counters) {
508 /* cannot select group header.. return null to indicate this
509 * main_ui():
510 */
511 if (n == current_cntr)
512 return NULL;
513 n++;
514 }
515
516 for (; j < group->group->num_counters; j++) {
517 if (n == current_cntr) {
518 if (ctr)
519 *ctr = j;
520 return group;
521 }
522 n++;
523 }
524 }
525
526 assert(0);
527 return NULL;
528 }
529
530 static void
counter_dialog(void)531 counter_dialog(void)
532 {
533 WINDOW *dialog;
534 struct counter_group *group;
535 int cnt = 0, current = 0, scroll;
536
537 /* figure out dialog size: */
538 int dh = h / 2;
539 int dw = ctr_width + 2;
540
541 group = current_counter(&cnt);
542
543 /* find currently selected idx (note there can be discontinuities
544 * so the selected value does not map 1:1 to current idx)
545 */
546 uint32_t selected = group->counter[cnt].select_val;
547 for (int i = 0; i < group->group->num_countables; i++) {
548 if (group->group->countables[i].selector == selected) {
549 current = i;
550 break;
551 }
552 }
553
554 /* scrolling offset, if dialog is too small for all the choices: */
555 scroll = 0;
556
557 dialog = newwin(dh, dw, (h - dh) / 2, (w - dw) / 2);
558 box(dialog, 0, 0);
559 wrefresh(dialog);
560 keypad(dialog, TRUE);
561
562 while (true) {
563 int max = MIN2(dh - 2, group->group->num_countables);
564 int selector = -1;
565
566 if ((current - scroll) >= (dh - 3)) {
567 scroll = current - (dh - 3);
568 } else if (current < scroll) {
569 scroll = current;
570 }
571
572 for (int i = 0; i < max; i++) {
573 int n = scroll + i;
574 wmove(dialog, i + 1, 1);
575 if (n == current) {
576 assert(n < group->group->num_countables);
577 selector = group->group->countables[n].selector;
578 wattron(dialog, COLOR_PAIR(COLOR_INVERSE));
579 }
580 if (n < group->group->num_countables)
581 waddstr(dialog, group->group->countables[n].name);
582 whline(dialog, ' ', dw - getcurx(dialog) - 1);
583 if (n == current)
584 wattroff(dialog, COLOR_PAIR(COLOR_INVERSE));
585 }
586
587 assert(selector >= 0);
588
589 switch (wgetch(dialog)) {
590 case KEY_UP:
591 current = MAX2(0, current - 1);
592 break;
593 case KEY_DOWN:
594 current = MIN2(group->group->num_countables - 1, current + 1);
595 break;
596 case KEY_LEFT:
597 case KEY_ENTER:
598 /* select new sampler */
599 select_counter(group, cnt, selector);
600 flush_ring();
601 config_save();
602 goto out;
603 case 'q':
604 goto out;
605 default:
606 /* ignore */
607 break;
608 }
609
610 resample();
611 }
612
613 out:
614 wborder(dialog, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ');
615 delwin(dialog);
616 }
617
618 static void
scroll_cntr(int amount)619 scroll_cntr(int amount)
620 {
621 if (amount < 0) {
622 current_cntr = MAX2(1, current_cntr + amount);
623 if (current_counter(NULL) == NULL) {
624 current_cntr = MAX2(1, current_cntr - 1);
625 }
626 } else {
627 current_cntr = MIN2(max_rows - 1, current_cntr + amount);
628 if (current_counter(NULL) == NULL)
629 current_cntr = MIN2(max_rows - 1, current_cntr + 1);
630 }
631 }
632
633 static void
main_ui(void)634 main_ui(void)
635 {
636 WINDOW *mainwin;
637 uint32_t last_time = gettime_us();
638
639 /* curses setup: */
640 mainwin = initscr();
641 if (!mainwin)
642 goto out;
643
644 cbreak();
645 wtimeout(mainwin, REFRESH_MS);
646 noecho();
647 keypad(mainwin, TRUE);
648 curs_set(0);
649 start_color();
650 init_pair(COLOR_GROUP_HEADER, COLOR_WHITE, COLOR_GREEN);
651 init_pair(COLOR_FOOTER, COLOR_WHITE, COLOR_BLUE);
652 init_pair(COLOR_INVERSE, COLOR_BLACK, COLOR_WHITE);
653
654 while (true) {
655 switch (wgetch(mainwin)) {
656 case KEY_UP:
657 scroll_cntr(-1);
658 break;
659 case KEY_DOWN:
660 scroll_cntr(+1);
661 break;
662 case KEY_NPAGE: /* page-down */
663 /* TODO figure out # of rows visible? */
664 scroll_cntr(+15);
665 break;
666 case KEY_PPAGE: /* page-up */
667 /* TODO figure out # of rows visible? */
668 scroll_cntr(-15);
669 break;
670 case KEY_RIGHT:
671 counter_dialog();
672 break;
673 case 'q':
674 goto out;
675 break;
676 default:
677 /* ignore */
678 break;
679 }
680 resample();
681 redraw(mainwin);
682
683 /* restore the counters every 0.5s in case the GPU has suspended,
684 * in which case the current selected countables will have reset:
685 */
686 uint32_t t = gettime_us();
687 if (delta(last_time, t) > 500000) {
688 restore_counter_groups();
689 flush_ring();
690 last_time = t;
691 }
692 }
693
694 /* restore settings.. maybe we need an atexit()??*/
695 out:
696 delwin(mainwin);
697 endwin();
698 refresh();
699 }
700
701 static void
restore_counter_groups(void)702 restore_counter_groups(void)
703 {
704 for (unsigned i = 0; i < dev.ngroups; i++) {
705 struct counter_group *group = &dev.groups[i];
706 unsigned j = 0;
707
708 /* NOTE skip CP the first CP counter */
709 if (i == 0)
710 j++;
711
712 for (; j < group->group->num_counters; j++) {
713 select_counter(group, j, group->counter[j].select_val);
714 }
715 }
716 }
717
718 static void
setup_counter_groups(const struct fd_perfcntr_group * groups)719 setup_counter_groups(const struct fd_perfcntr_group *groups)
720 {
721 for (unsigned i = 0; i < dev.ngroups; i++) {
722 struct counter_group *group = &dev.groups[i];
723
724 group->group = &groups[i];
725
726 max_rows += group->group->num_counters + 1;
727
728 /* the first CP counter is hidden: */
729 if (i == 0) {
730 max_rows--;
731 if (group->group->num_counters <= 1)
732 max_rows--;
733 }
734
735 for (unsigned j = 0; j < group->group->num_counters; j++) {
736 group->counter[j].counter = &group->group->counters[j];
737
738 group->counter[j].val_hi =
739 dev.io + (group->counter[j].counter->counter_reg_hi * 4);
740 group->counter[j].val_lo =
741 dev.io + (group->counter[j].counter->counter_reg_lo * 4);
742
743 group->counter[j].select_val = j;
744 }
745
746 for (unsigned j = 0; j < group->group->num_countables; j++) {
747 ctr_width =
748 MAX2(ctr_width, strlen(group->group->countables[j].name) + 1);
749 }
750 }
751 }
752
753 /*
754 * configuration / persistence
755 */
756
757 static config_t cfg;
758 static config_setting_t *setting;
759
760 static void
config_save(void)761 config_save(void)
762 {
763 for (unsigned i = 0; i < dev.ngroups; i++) {
764 struct counter_group *group = &dev.groups[i];
765 unsigned j = 0;
766
767 /* NOTE skip CP the first CP counter */
768 if (i == 0)
769 j++;
770
771 config_setting_t *sect =
772 config_setting_get_member(setting, group->group->name);
773
774 for (; j < group->group->num_counters; j++) {
775 char name[] = "counter0000";
776 sprintf(name, "counter%d", j);
777 config_setting_t *s = config_setting_lookup(sect, name);
778 config_setting_set_int(s, group->counter[j].select_val);
779 }
780 }
781
782 config_write_file(&cfg, "fdperf.cfg");
783 }
784
785 static void
config_restore(void)786 config_restore(void)
787 {
788 char *str;
789
790 config_init(&cfg);
791
792 /* Read the file. If there is an error, report it and exit. */
793 if (!config_read_file(&cfg, "fdperf.cfg")) {
794 warn("could not restore settings");
795 }
796
797 config_setting_t *root = config_root_setting(&cfg);
798
799 /* per device settings: */
800 (void)asprintf(&str, "a%dxx", dev.chipid >> 24);
801 setting = config_setting_get_member(root, str);
802 if (!setting)
803 setting = config_setting_add(root, str, CONFIG_TYPE_GROUP);
804 free(str);
805
806 for (unsigned i = 0; i < dev.ngroups; i++) {
807 struct counter_group *group = &dev.groups[i];
808 unsigned j = 0;
809
810 /* NOTE skip CP the first CP counter */
811 if (i == 0)
812 j++;
813
814 config_setting_t *sect =
815 config_setting_get_member(setting, group->group->name);
816
817 if (!sect) {
818 sect =
819 config_setting_add(setting, group->group->name, CONFIG_TYPE_GROUP);
820 }
821
822 for (; j < group->group->num_counters; j++) {
823 char name[] = "counter0000";
824 sprintf(name, "counter%d", j);
825 config_setting_t *s = config_setting_lookup(sect, name);
826 if (!s) {
827 config_setting_add(sect, name, CONFIG_TYPE_INT);
828 continue;
829 }
830 select_counter(group, j, config_setting_get_int(s));
831 }
832 }
833 }
834
835 /*
836 * main
837 */
838
839 int
main(int argc,char ** argv)840 main(int argc, char **argv)
841 {
842 find_device();
843
844 const struct fd_perfcntr_group *groups;
845 struct fd_dev_id dev_id = {
846 .gpu_id = (dev.chipid >> 24) * 100,
847 };
848 groups = fd_perfcntrs(&dev_id, &dev.ngroups);
849 if (!groups) {
850 errx(1, "no perfcntr support");
851 }
852
853 dev.groups = calloc(dev.ngroups, sizeof(struct counter_group));
854
855 setlocale(LC_NUMERIC, "en_US.UTF-8");
856
857 setup_counter_groups(groups);
858 restore_counter_groups();
859 config_restore();
860 flush_ring();
861
862 main_ui();
863
864 return 0;
865 }
866