Home | History | Annotate | Line # | Download | only in perfcntrs
      1 /*
      2  * Copyright (C) 2016 Rob Clark <robclark (at) 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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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 *
    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
    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
    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
    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
    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
    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
    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
    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
    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