5b4488209c5a7583e056cf65b2808ebe7995f46c
2 * Copyright (C) 2016 Rob Clark <robclark@freedesktop.org>
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:
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
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.
25 #include <arpa/inet.h>
35 #include <sys/types.h>
41 #include <libconfig.h>
45 #include "drm/freedreno_drmif.h"
46 #include "drm/freedreno_ringbuffer.h"
48 #include "freedreno_perfcntr.h"
50 #define MAX_CNTR_PER_GROUP 24
52 /* NOTE first counter group should always be CP, since we unconditionally
53 * use CP counter to measure the gpu freq.
56 struct counter_group
{
57 const struct fd_perfcntr_group
*group
;
60 const struct fd_perfcntr_counter
*counter
;
62 volatile uint32_t *val_hi
;
63 volatile uint32_t *val_lo
;
64 } counter
[MAX_CNTR_PER_GROUP
];
66 /* last sample time: */
67 uint32_t stime
[MAX_CNTR_PER_GROUP
];
68 /* for now just care about the low 32b value.. at least then we don't
69 * have to really care that we can't sample both hi and lo regs at the
72 uint32_t last
[MAX_CNTR_PER_GROUP
];
73 /* current value, ie. by how many did the counter increase in last
74 * sampling period divided by the sampling period:
76 float current
[MAX_CNTR_PER_GROUP
];
77 /* name of currently selected counters (for UI): */
78 const char *label
[MAX_CNTR_PER_GROUP
];
83 int address_cells
, size_cells
;
90 /* per-generation table of counters: */
92 struct counter_group
*groups
;
93 /* drm device (for writing select regs via ring): */
94 struct fd_device
*dev
;
96 struct fd_submit
*submit
;
97 struct fd_ringbuffer
*ring
;
100 static void config_save(void);
101 static void config_restore(void);
102 static void restore_counter_groups(void);
111 readfile(const char *path
, int *sz
)
116 fd
= open(path
, O_RDONLY
);
121 buf
= realloc(buf
, n
+ CHUNKSIZE
);
122 ret
= read(fd
, buf
+ n
, CHUNKSIZE
);
127 } else if (ret
< CHUNKSIZE
) {
141 clock_gettime(CLOCK_MONOTONIC
, &ts
);
142 return (ts
.tv_sec
* 1000000) + (ts
.tv_nsec
/ 1000);
146 delta(uint32_t a
, uint32_t b
)
148 /* deal with rollover: */
150 return 0xffffffff - a
+ b
;
156 * TODO de-duplicate OUT_RING() and friends
159 #define CP_WAIT_FOR_IDLE 38
160 #define CP_TYPE0_PKT 0x00000000
161 #define CP_TYPE3_PKT 0xc0000000
162 #define CP_TYPE4_PKT 0x40000000
163 #define CP_TYPE7_PKT 0x70000000
166 OUT_RING(struct fd_ringbuffer
*ring
, uint32_t data
)
168 *(ring
->cur
++) = data
;
172 OUT_PKT0(struct fd_ringbuffer
*ring
, uint16_t regindx
, uint16_t cnt
)
174 OUT_RING(ring
, CP_TYPE0_PKT
| ((cnt
-1) << 16) | (regindx
& 0x7FFF));
178 OUT_PKT3(struct fd_ringbuffer
*ring
, uint8_t opcode
, uint16_t cnt
)
180 OUT_RING(ring
, CP_TYPE3_PKT
| ((cnt
-1) << 16) | ((opcode
& 0xFF) << 8));
185 * Starting with a5xx, pkt4/pkt7 are used instead of pkt0/pkt3
188 static inline unsigned
189 _odd_parity_bit(unsigned val
)
191 /* See: http://graphics.stanford.edu/~seander/bithacks.html#ParityParallel
192 * note that we want odd parity so 0x6996 is inverted.
198 return (~0x6996 >> val
) & 1;
202 OUT_PKT4(struct fd_ringbuffer
*ring
, uint16_t regindx
, uint16_t cnt
)
204 OUT_RING(ring
, CP_TYPE4_PKT
| cnt
|
205 (_odd_parity_bit(cnt
) << 7) |
206 ((regindx
& 0x3ffff) << 8) |
207 ((_odd_parity_bit(regindx
) << 27)));
211 OUT_PKT7(struct fd_ringbuffer
*ring
, uint8_t opcode
, uint16_t cnt
)
213 OUT_RING(ring
, CP_TYPE7_PKT
| cnt
|
214 (_odd_parity_bit(cnt
) << 15) |
215 ((opcode
& 0x7f) << 16) |
216 ((_odd_parity_bit(opcode
) << 23)));
220 * code to find stuff in /proc/device-tree:
222 * NOTE: if we sampled the counters from the cmdstream, we could avoid needing
223 * /dev/mem and /proc/device-tree crawling. OTOH when the GPU is heavily loaded
224 * we would be competing with whatever else is using the GPU.
228 readdt(const char *node
)
234 (void) asprintf(&path
, "%s/%s", dev
.dtnode
, node
);
235 buf
= readfile(path
, &sz
);
242 find_freqs_fn(const char *fpath
, const struct stat
*sb
, int typeflag
, struct FTW
*ftwbuf
)
244 const char *fname
= fpath
+ ftwbuf
->base
;
247 if (strcmp(fname
, "qcom,gpu-freq") == 0) {
248 uint32_t *buf
= readfile(fpath
, &sz
);
249 uint32_t freq
= ntohl(buf
[0]);
251 dev
.max_freq
= MAX2(dev
.max_freq
, freq
);
252 dev
.min_freq
= MIN2(dev
.min_freq
, freq
);
267 (void) asprintf(&path
, "%s/%s", dev
.dtnode
, "qcom,gpu-pwrlevels");
269 ret
= nftw(path
, find_freqs_fn
, 64, 0);
271 err(1, "could not find power levels");
277 find_device_fn(const char *fpath
, const struct stat
*sb
, int typeflag
, struct FTW
*ftwbuf
)
279 const char *fname
= fpath
+ ftwbuf
->base
;
282 if (strcmp(fname
, "compatible") == 0) {
283 char *str
= readfile(fpath
, &sz
);
284 if ((strcmp(str
, "qcom,adreno-3xx") == 0) ||
285 (strcmp(str
, "qcom,kgsl-3d0") == 0) ||
286 (strstr(str
, "amd,imageon") == str
) ||
287 (strstr(str
, "qcom,adreno") == str
)) {
288 int dlen
= strlen(fpath
) - strlen("/compatible");
289 dev
.dtnode
= malloc(dlen
+ 1);
290 memcpy(dev
.dtnode
, fpath
, dlen
);
291 printf("found dt node: %s\n", dev
.dtnode
);
293 char buf
[dlen
+ sizeof("/../#address-cells") + 1];
296 sprintf(buf
, "%s/../#address-cells", dev
.dtnode
);
297 val
= readfile(buf
, &sz
);
298 dev
.address_cells
= ntohl(*val
);
301 sprintf(buf
, "%s/../#size-cells", dev
.dtnode
);
302 val
= readfile(buf
, &sz
);
303 dev
.size_cells
= ntohl(*val
);
306 printf("#address-cells=%d, #size-cells=%d\n",
307 dev
.address_cells
, dev
.size_cells
);
324 ret
= nftw("/proc/device-tree/", find_device_fn
, 64, 0);
326 err(1, "could not find adreno gpu");
329 errx(1, "could not find qcom,adreno-3xx node");
331 fd
= drmOpen("msm", NULL
);
333 err(1, "could not open drm device");
335 dev
.dev
= fd_device_new(fd
);
336 dev
.pipe
= fd_pipe_new(dev
.dev
, FD_PIPE_3D
);
339 ret
= fd_pipe_get_param(dev
.pipe
, FD_CHIP_ID
, &val
);
341 err(1, "could not get gpu-id");
345 #define CHIP_FMT "d%d%d.%d"
346 #define CHIP_ARGS(chipid) \
347 ((chipid) >> 24) & 0xff, \
348 ((chipid) >> 16) & 0xff, \
349 ((chipid) >> 8) & 0xff, \
350 ((chipid) >> 0) & 0xff
351 printf("device: a%"CHIP_FMT
"\n", CHIP_ARGS(dev
.chipid
));
353 b
= buf
= readdt("reg");
355 if (dev
.address_cells
== 2) {
356 uint32_t u
[2] = { ntohl(buf
[0]), ntohl(buf
[1]) };
357 dev
.base
= (((uint64_t)u
[0]) << 32) | u
[1];
360 dev
.base
= ntohl(buf
[0]);
364 if (dev
.size_cells
== 2) {
365 uint32_t u
[2] = { ntohl(buf
[0]), ntohl(buf
[1]) };
366 dev
.size
= (((uint64_t)u
[0]) << 32) | u
[1];
369 dev
.size
= ntohl(buf
[0]);
375 printf("i/o region at %08"PRIu64
" (size: %x)\n", dev
.base
, dev
.size
);
377 /* try MAX_FREQ first as that will work regardless of old dt
378 * dt bindings vs upstream bindings:
380 ret
= fd_pipe_get_param(dev
.pipe
, FD_MAX_FREQ
, &val
);
382 printf("falling back to parsing DT bindings for freq\n");
389 printf("min_freq=%u, max_freq=%u\n", dev
.min_freq
, dev
.max_freq
);
391 fd
= open("/dev/mem", O_RDWR
| O_SYNC
);
393 err(1, "could not open /dev/mem");
395 dev
.io
= mmap(0, dev
.size
, PROT_READ
| PROT_WRITE
, MAP_SHARED
, fd
, dev
.base
);
397 err(1, "could not map device");
412 ret
= fd_submit_flush(dev
.submit
, -1, NULL
, NULL
);
414 errx(1, "submit failed: %d", ret
);
415 fd_ringbuffer_del(dev
.ring
);
416 fd_submit_del(dev
.submit
);
423 select_counter(struct counter_group
*group
, int ctr
, int n
)
425 assert(n
< group
->group
->num_countables
);
426 assert(ctr
< group
->group
->num_counters
);
428 group
->label
[ctr
] = group
->group
->countables
[n
].name
;
429 group
->counter
[ctr
].select_val
= n
;
432 dev
.submit
= fd_submit_new(dev
.pipe
);
433 dev
.ring
= fd_submit_new_ringbuffer(dev
.submit
, 0x1000,
434 FD_RINGBUFFER_PRIMARY
| FD_RINGBUFFER_GROWABLE
);
437 /* bashing select register directly while gpu is active will end
438 * in tears.. so we need to write it via the ring:
440 * TODO it would help startup time, if gpu is loaded, to batch
441 * all the initial writes and do a single flush.. although that
442 * makes things more complicated for capturing inital sample value
444 struct fd_ringbuffer
*ring
= dev
.ring
;
445 switch (dev
.chipid
>> 24) {
449 OUT_PKT3(ring
, CP_WAIT_FOR_IDLE
, 1);
450 OUT_RING(ring
, 0x00000000);
452 if (group
->group
->counters
[ctr
].enable
) {
453 OUT_PKT0(ring
, group
->group
->counters
[ctr
].enable
, 1);
457 if (group
->group
->counters
[ctr
].clear
) {
458 OUT_PKT0(ring
, group
->group
->counters
[ctr
].clear
, 1);
461 OUT_PKT0(ring
, group
->group
->counters
[ctr
].clear
, 1);
465 OUT_PKT0(ring
, group
->group
->counters
[ctr
].select_reg
, 1);
468 if (group
->group
->counters
[ctr
].enable
) {
469 OUT_PKT0(ring
, group
->group
->counters
[ctr
].enable
, 1);
476 OUT_PKT7(ring
, CP_WAIT_FOR_IDLE
, 0);
478 if (group
->group
->counters
[ctr
].enable
) {
479 OUT_PKT4(ring
, group
->group
->counters
[ctr
].enable
, 1);
483 if (group
->group
->counters
[ctr
].clear
) {
484 OUT_PKT4(ring
, group
->group
->counters
[ctr
].clear
, 1);
487 OUT_PKT4(ring
, group
->group
->counters
[ctr
].clear
, 1);
491 OUT_PKT4(ring
, group
->group
->counters
[ctr
].select_reg
, 1);
494 if (group
->group
->counters
[ctr
].enable
) {
495 OUT_PKT4(ring
, group
->group
->counters
[ctr
].enable
, 1);
502 group
->last
[ctr
] = *group
->counter
[ctr
].val_lo
;
503 group
->stime
[ctr
] = gettime_us();
507 resample_counter(struct counter_group
*group
, int ctr
)
509 uint32_t val
= *group
->counter
[ctr
].val_lo
;
510 uint32_t t
= gettime_us();
511 uint32_t dt
= delta(group
->stime
[ctr
], t
);
512 uint32_t dval
= delta(group
->last
[ctr
], val
);
513 group
->current
[ctr
] = (float)dval
* 1000000.0 / (float)dt
;
514 group
->last
[ctr
] = val
;
515 group
->stime
[ctr
] = t
;
518 #define REFRESH_MS 500
520 /* sample all the counters: */
524 static uint64_t last_time
;
525 uint64_t current_time
= gettime_us();
527 if ((current_time
- last_time
) < (REFRESH_MS
* 1000 / 2))
530 last_time
= current_time
;
532 for (unsigned i
= 0; i
< dev
.ngroups
; i
++) {
533 struct counter_group
*group
= &dev
.groups
[i
];
534 for (unsigned j
= 0; j
< group
->group
->num_counters
; j
++) {
535 resample_counter(group
, j
);
544 #define COLOR_GROUP_HEADER 1
545 #define COLOR_FOOTER 2
546 #define COLOR_INVERSE 3
549 static int ctr_width
;
550 static int max_rows
, current_cntr
= 1;
553 redraw_footer(WINDOW
*win
)
558 n
= asprintf(&footer
, " fdperf: a%"CHIP_FMT
" (%.2fMHz..%.2fMHz)",
559 CHIP_ARGS(dev
.chipid
),
560 ((float)dev
.min_freq
) / 1000000.0,
561 ((float)dev
.max_freq
) / 1000000.0);
563 wmove(win
, h
- 1, 0);
564 wattron(win
, COLOR_PAIR(COLOR_FOOTER
));
565 waddstr(win
, footer
);
566 whline(win
, ' ', w
- n
);
567 wattroff(win
, COLOR_PAIR(COLOR_FOOTER
));
573 redraw_group_header(WINDOW
*win
, int row
, const char *name
)
576 wattron(win
, A_BOLD
);
577 wattron(win
, COLOR_PAIR(COLOR_GROUP_HEADER
));
579 whline(win
, ' ', w
- strlen(name
));
580 wattroff(win
, COLOR_PAIR(COLOR_GROUP_HEADER
));
581 wattroff(win
, A_BOLD
);
585 redraw_counter_label(WINDOW
*win
, int row
, const char *name
, bool selected
)
587 int n
= strlen(name
);
588 assert(n
<= ctr_width
);
590 whline(win
, ' ', ctr_width
- n
);
591 wmove(win
, row
, ctr_width
- n
);
593 wattron(win
, COLOR_PAIR(COLOR_INVERSE
));
596 wattroff(win
, COLOR_PAIR(COLOR_INVERSE
));
601 redraw_counter_value_cycles(WINDOW
*win
, float val
)
604 int x
= getcurx(win
);
605 int valwidth
= w
- x
;
608 /* convert to fraction of max freq: */
609 val
= val
/ (float)dev
.max_freq
;
611 /* figure out percentage-bar width: */
612 barwidth
= (int)(val
* valwidth
);
614 /* sometimes things go over 100%.. idk why, could be
615 * things running faster than base clock, or counter
616 * summing up cycles in multiple cores?
618 barwidth
= MIN2(barwidth
, valwidth
- 1);
620 n
= asprintf(&str
, "%.2f%%", 100.0 * val
);
621 wattron(win
, COLOR_PAIR(COLOR_INVERSE
));
622 waddnstr(win
, str
, barwidth
);
624 whline(win
, ' ', barwidth
- n
);
625 wmove(win
, getcury(win
), x
+ barwidth
);
627 wattroff(win
, COLOR_PAIR(COLOR_INVERSE
));
629 waddstr(win
, str
+ barwidth
);
630 whline(win
, ' ', w
- getcurx(win
));
636 redraw_counter_value_raw(WINDOW
*win
, float val
)
639 (void) asprintf(&str
, "%'.2f", val
);
641 whline(win
, ' ', w
- getcurx(win
));
646 redraw_counter(WINDOW
*win
, int row
, struct counter_group
*group
,
647 int ctr
, bool selected
)
649 redraw_counter_label(win
, row
, group
->label
[ctr
], selected
);
651 /* quick hack, if the label has "CYCLE" in the name, it is
652 * probably a cycle counter ;-)
653 * Perhaps add more info in rnndb schema to know how to
654 * treat individual counters (ie. which are cycles, and
655 * for those we want to present as a percentage do we
656 * need to scale the result.. ie. is it running at some
657 * multiple or divisor of core clk, etc)
659 * TODO it would be much more clever to get this from xml
660 * Also.. in some cases I think we want to know how many
661 * units the counter is counting for, ie. if a320 has 2x
662 * shader as a306 we might need to scale the result..
664 if (strstr(group
->label
[ctr
], "CYCLE") ||
665 strstr(group
->label
[ctr
], "BUSY") ||
666 strstr(group
->label
[ctr
], "IDLE"))
667 redraw_counter_value_cycles(win
, group
->current
[ctr
]);
669 redraw_counter_value_raw(win
, group
->current
[ctr
]);
675 static int scroll
= 0;
683 if ((current_cntr
- scroll
) > (max
- 1)) {
684 scroll
= current_cntr
- (max
- 1);
685 } else if ((current_cntr
- 1) < scroll
) {
686 scroll
= current_cntr
- 1;
689 for (unsigned i
= 0; i
< dev
.ngroups
; i
++) {
690 struct counter_group
*group
= &dev
.groups
[i
];
693 /* NOTE skip CP the first CP counter */
697 if (j
< group
->group
->num_counters
) {
698 if ((scroll
<= row
) && ((row
- scroll
) < max
))
699 redraw_group_header(win
, row
- scroll
, group
->group
->name
);
703 for (; j
< group
->group
->num_counters
; j
++) {
704 if ((scroll
<= row
) && ((row
- scroll
) < max
))
705 redraw_counter(win
, row
- scroll
, group
, j
, row
== current_cntr
);
710 /* convert back to physical (unscrolled) offset: */
713 redraw_group_header(win
, row
, "Status");
716 /* Draw GPU freq row: */
717 redraw_counter_label(win
, row
, "Freq (MHz)", false);
718 redraw_counter_value_raw(win
, dev
.groups
[0].current
[0] / 1000000.0);
726 static struct counter_group
*
727 current_counter(int *ctr
)
731 for (unsigned i
= 0; i
< dev
.ngroups
; i
++) {
732 struct counter_group
*group
= &dev
.groups
[i
];
735 /* NOTE skip the first CP counter (CP_ALWAYS_COUNT) */
739 /* account for group header: */
740 if (j
< group
->group
->num_counters
) {
741 /* cannot select group header.. return null to indicate this
744 if (n
== current_cntr
)
750 for (; j
< group
->group
->num_counters
; j
++) {
751 if (n
== current_cntr
) {
768 struct counter_group
*group
;
769 int cnt
, current
= 0, scroll
;
771 /* figure out dialog size: */
773 int dw
= ctr_width
+ 2;
775 group
= current_counter(&cnt
);
777 /* find currently selected idx (note there can be discontinuities
778 * so the selected value does not map 1:1 to current idx)
780 uint32_t selected
= group
->counter
[cnt
].select_val
;
781 for (int i
= 0; i
< group
->group
->num_countables
; i
++) {
782 if (group
->group
->countables
[i
].selector
== selected
) {
788 /* scrolling offset, if dialog is too small for all the choices: */
791 dialog
= newwin(dh
, dw
, (h
-dh
)/2, (w
-dw
)/2);
794 keypad(dialog
, TRUE
);
797 int max
= MIN2(dh
- 2, group
->group
->num_countables
);
800 if ((current
- scroll
) >= (dh
- 3)) {
801 scroll
= current
- (dh
- 3);
802 } else if (current
< scroll
) {
806 for (int i
= 0; i
< max
; i
++) {
808 wmove(dialog
, i
+1, 1);
810 assert (n
< group
->group
->num_countables
);
811 selector
= group
->group
->countables
[n
].selector
;
812 wattron(dialog
, COLOR_PAIR(COLOR_INVERSE
));
814 if (n
< group
->group
->num_countables
)
815 waddstr(dialog
, group
->group
->countables
[n
].name
);
816 whline(dialog
, ' ', dw
- getcurx(dialog
) - 1);
818 wattroff(dialog
, COLOR_PAIR(COLOR_INVERSE
));
821 assert (selector
>= 0);
823 switch (wgetch(dialog
)) {
825 current
= MAX2(0, current
- 1);
828 current
= MIN2(group
->group
->num_countables
- 1, current
+ 1);
832 /* select new sampler */
833 select_counter(group
, cnt
, selector
);
848 wborder(dialog
, ' ', ' ', ' ',' ',' ',' ',' ',' ');
853 scroll_cntr(int amount
)
856 current_cntr
= MAX2(1, current_cntr
+ amount
);
857 if (current_counter(NULL
) == NULL
) {
858 current_cntr
= MAX2(1, current_cntr
- 1);
861 current_cntr
= MIN2(max_rows
- 1, current_cntr
+ amount
);
862 if (current_counter(NULL
) == NULL
)
863 current_cntr
= MIN2(max_rows
- 1, current_cntr
+ 1);
871 uint32_t last_time
= gettime_us();
879 wtimeout(mainwin
, REFRESH_MS
);
881 keypad(mainwin
, TRUE
);
884 init_pair(COLOR_GROUP_HEADER
, COLOR_WHITE
, COLOR_GREEN
);
885 init_pair(COLOR_FOOTER
, COLOR_WHITE
, COLOR_BLUE
);
886 init_pair(COLOR_INVERSE
, COLOR_BLACK
, COLOR_WHITE
);
889 switch (wgetch(mainwin
)) {
896 case KEY_NPAGE
: /* page-down */
897 /* TODO figure out # of rows visible? */
900 case KEY_PPAGE
: /* page-up */
901 /* TODO figure out # of rows visible? */
917 /* restore the counters every 0.5s in case the GPU has suspended,
918 * in which case the current selected countables will have reset:
920 uint32_t t
= gettime_us();
921 if (delta(last_time
, t
) > 500000) {
922 restore_counter_groups();
928 /* restore settings.. maybe we need an atexit()??*/
936 restore_counter_groups(void)
938 for (unsigned i
= 0; i
< dev
.ngroups
; i
++) {
939 struct counter_group
*group
= &dev
.groups
[i
];
942 /* NOTE skip CP the first CP counter */
946 for (; j
< group
->group
->num_counters
; j
++) {
947 select_counter(group
, j
, group
->counter
[j
].select_val
);
953 setup_counter_groups(const struct fd_perfcntr_group
*groups
)
955 for (unsigned i
= 0; i
< dev
.ngroups
; i
++) {
956 struct counter_group
*group
= &dev
.groups
[i
];
958 group
->group
= &groups
[i
];
960 max_rows
+= group
->group
->num_counters
+ 1;
962 /* the first CP counter is hidden: */
965 if (group
->group
->num_counters
<= 1)
969 for (unsigned j
= 0; j
< group
->group
->num_counters
; j
++) {
970 group
->counter
[j
].counter
= &group
->group
->counters
[j
];
972 group
->counter
[j
].val_hi
= dev
.io
+ (group
->counter
[j
].counter
->counter_reg_hi
* 4);
973 group
->counter
[j
].val_lo
= dev
.io
+ (group
->counter
[j
].counter
->counter_reg_lo
* 4);
975 group
->counter
[j
].select_val
= j
;
978 for (unsigned j
= 0; j
< group
->group
->num_countables
; j
++) {
979 ctr_width
= MAX2(ctr_width
, strlen(group
->group
->countables
[j
].name
) + 1);
985 * configuration / persistence
989 static config_setting_t
*setting
;
994 for (unsigned i
= 0; i
< dev
.ngroups
; i
++) {
995 struct counter_group
*group
= &dev
.groups
[i
];
998 /* NOTE skip CP the first CP counter */
1002 config_setting_t
*sect
=
1003 config_setting_get_member(setting
, group
->group
->name
);
1005 for (; j
< group
->group
->num_counters
; j
++) {
1006 char name
[] = "counter0000";
1007 sprintf(name
, "counter%d", j
);
1008 config_setting_t
*s
=
1009 config_setting_lookup(sect
, name
);
1010 config_setting_set_int(s
, group
->counter
[j
].select_val
);
1014 config_write_file(&cfg
, "fdperf.cfg");
1018 config_restore(void)
1024 /* Read the file. If there is an error, report it and exit. */
1025 if(!config_read_file(&cfg
, "fdperf.cfg")) {
1026 warn("could not restore settings");
1029 config_setting_t
*root
= config_root_setting(&cfg
);
1031 /* per device settings: */
1032 (void) asprintf(&str
, "a%dxx", dev
.chipid
>> 24);
1033 setting
= config_setting_get_member(root
, str
);
1035 setting
= config_setting_add(root
, str
, CONFIG_TYPE_GROUP
);
1038 for (unsigned i
= 0; i
< dev
.ngroups
; i
++) {
1039 struct counter_group
*group
= &dev
.groups
[i
];
1042 /* NOTE skip CP the first CP counter */
1046 config_setting_t
*sect
=
1047 config_setting_get_member(setting
, group
->group
->name
);
1050 sect
= config_setting_add(setting
, group
->group
->name
,
1054 for (; j
< group
->group
->num_counters
; j
++) {
1055 char name
[] = "counter0000";
1056 sprintf(name
, "counter%d", j
);
1057 config_setting_t
*s
= config_setting_lookup(sect
, name
);
1059 config_setting_add(sect
, name
, CONFIG_TYPE_INT
);
1062 select_counter(group
, j
, config_setting_get_int(s
));
1072 main(int argc
, char **argv
)
1076 const struct fd_perfcntr_group
*groups
;
1077 groups
= fd_perfcntrs((dev
.chipid
>> 24) * 100, &dev
.ngroups
);
1079 errx(1, "no perfcntr support");
1082 dev
.groups
= calloc(dev
.ngroups
, sizeof(struct counter_group
));
1084 setup_counter_groups(groups
);
1085 restore_counter_groups();