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