1/*
2 * Copyright (c) 2012 Rob Clark <robdclark@gmail.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 * SOFTWARE.
22 */
23
24#include <assert.h>
25#include <ctype.h>
26#include <err.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <getopt.h>
30#include <signal.h>
31#include <stdarg.h>
32#include <stdbool.h>
33#include <stdint.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <unistd.h>
38#include <sys/stat.h>
39#include <sys/types.h>
40#include <sys/wait.h>
41
42#include "buffers.h"
43#include "cffdec.h"
44#include "disasm.h"
45#include "io.h"
46#include "pager.h"
47#include "redump.h"
48#include "rnnutil.h"
49#include "script.h"
50
51static struct cffdec_options options = {
52   .gpu_id = 220,
53};
54
55static bool needs_wfi = false;
56static bool is_blob = false;
57static int show_comp = false;
58static int interactive;
59static int vertices;
60static const char *exename;
61
62static int handle_file(const char *filename, int start, int end, int draw);
63
64static void
65print_usage(const char *name)
66{
67   /* clang-format off */
68   fprintf(stderr, "Usage:\n\n"
69           "\t%s [OPTSIONS]... FILE...\n\n"
70           "Options:\n"
71           "\t-v, --verbose    - more verbose disassembly\n"
72           "\t--dump-shaders   - dump each shader to a raw file\n"
73           "\t--no-color       - disable colorized output (default for non-console\n"
74           "\t                   output)\n"
75           "\t--color          - enable colorized output (default for tty output)\n"
76           "\t--no-pager       - disable pager (default for non-console output)\n"
77           "\t--pager          - enable pager (default for tty output)\n"
78           "\t-s, --summary    - don't show individual register writes, but just\n"
79           "\t                   register values on draws\n"
80           "\t-a, --allregs    - show all registers (including ones not written\n"
81           "\t                   since previous draw) on each draw\n"
82           "\t-S, --start=N    - start decoding from frame N\n"
83           "\t-E, --end=N      - stop decoding after frame N\n"
84           "\t-F, --frame=N    - decode only frame N\n"
85           "\t-D, --draw=N     - decode only draw N\n"
86           "\t-e, --exe=NAME   - only decode cmdstream from named process\n"
87           "\t--textures       - dump texture contents (if possible)\n"
88           "\t-L, --script=LUA - run specified lua script to analyze state\n"
89           "\t-q, --query=REG  - query mode, dump only specified query registers on\n"
90           "\t                   each draw; multiple --query/-q args can be given to\n"
91           "\t                   dump multiple registers; register can be specified\n"
92           "\t                   either by name or numeric offset\n"
93           "\t--query-all      - in query mode, show all queried regs on each draw\n"
94           "\t                   (default query mode)\n"
95           "\t--query-written  - in query mode, show queried regs on draws if any of\n"
96           "\t                   them have been written since previous draw\n"
97           "\t--query-delta    - in query mode, show queried regs on draws if any of\n"
98           "\t                   them have changed since previous draw\n"
99           "\t--query-compare  - dump registers for BINNING vs GMEM/BYPASS per draw;\n"
100           "\t                   only applicable for regs set via SDS group (a6xx+),\n"
101           "\t                   implies --once, can be combined with --query-all,\n"
102           "\t                   --query-written, or --query-delta\n"
103           "\t--once           - decode cmdstream only once (per draw mode); if same\n"
104           "\t                   cmdstream is executed for each tile, this will decode\n"
105           "\t                   it only for the first tile and skip the remainder,\n"
106           "\t                   which can be useful when looking at state that does\n"
107           "\t                   not change per tile\n"
108           "\t--not-once       - decode cmdstream for each IB (default)\n"
109           "\t--unit-test      - make reproducible output for unit testing\n"
110           "\t-h, --help       - show this message\n"
111           , name);
112   /* clang-format on */
113   exit(2);
114}
115
116/* clang-format off */
117static const struct option opts[] = {
118      /* Long opts that simply set a flag (no corresponding short alias: */
119      { "dump-shaders",    no_argument, &options.dump_shaders,  1 },
120      { "no-color",        no_argument, &options.color,         0 },
121      { "color",           no_argument, &options.color,         1 },
122      { "no-pager",        no_argument, &interactive,           0 },
123      { "pager",           no_argument, &interactive,           1 },
124      { "textures",        no_argument, &options.dump_textures, 1 },
125      { "show-compositor", no_argument, &show_comp,             1 },
126      { "query-all",       no_argument, &options.query_mode,    QUERY_ALL },
127      { "query-written",   no_argument, &options.query_mode,    QUERY_WRITTEN },
128      { "query-delta",     no_argument, &options.query_mode,    QUERY_DELTA },
129      { "query-compare",   no_argument, &options.query_compare, 1 },
130      { "once",            no_argument, &options.once,          1 },
131      { "not-once",        no_argument, &options.once,          0 },
132      { "unit-test",       no_argument, &options.unit_test,     1 },
133
134      /* Long opts with short alias: */
135      { "verbose",   no_argument,       0, 'v' },
136      { "summary",   no_argument,       0, 's' },
137      { "allregs",   no_argument,       0, 'a' },
138      { "start",     required_argument, 0, 'S' },
139      { "end",       required_argument, 0, 'E' },
140      { "frame",     required_argument, 0, 'F' },
141      { "draw",      required_argument, 0, 'D' },
142      { "exe",       required_argument, 0, 'e' },
143      { "script",    required_argument, 0, 'L' },
144      { "query",     required_argument, 0, 'q' },
145      { "help",      no_argument,       0, 'h' },
146};
147/* clang-format on */
148
149int
150main(int argc, char **argv)
151{
152   enum debug_t debug = PRINT_RAW | PRINT_STATS;
153   int ret = -1;
154   int start = 0, end = 0x7ffffff, draw = -1;
155   int c;
156
157   interactive = isatty(STDOUT_FILENO);
158
159   options.color = interactive;
160
161   while ((c = getopt_long(argc, argv, "vsaS:E:F:D:e:L:q:h", opts, NULL)) !=
162          -1) {
163      switch (c) {
164      case 0:
165         /* option that set a flag, nothing to do */
166         break;
167      case 'v':
168         debug |= (PRINT_RAW | EXPAND_REPEAT | PRINT_VERBOSE);
169         break;
170      case 's':
171         options.summary = true;
172         break;
173      case 'a':
174         options.allregs = true;
175         break;
176      case 'S':
177         start = atoi(optarg);
178         break;
179      case 'E':
180         end = atoi(optarg);
181         break;
182      case 'F':
183         start = end = atoi(optarg);
184         break;
185      case 'D':
186         draw = atoi(optarg);
187         break;
188      case 'e':
189         exename = optarg;
190         break;
191      case 'L':
192         options.script = optarg;
193         if (script_load(options.script)) {
194            errx(-1, "error loading %s\n", options.script);
195         }
196         break;
197      case 'q':
198         options.querystrs =
199            realloc(options.querystrs,
200                    (options.nquery + 1) * sizeof(*options.querystrs));
201         options.querystrs[options.nquery] = optarg;
202         options.nquery++;
203         interactive = 0;
204         break;
205      case 'h':
206      default:
207         print_usage(argv[0]);
208      }
209   }
210
211   disasm_a2xx_set_debug(debug);
212   disasm_a3xx_set_debug(debug);
213
214   if (interactive) {
215      pager_open();
216   }
217
218   while (optind < argc) {
219      ret = handle_file(argv[optind], start, end, draw);
220      if (ret) {
221         fprintf(stderr, "error reading: %s\n", argv[optind]);
222         fprintf(stderr, "continuing..\n");
223      }
224      optind++;
225   }
226
227   if (ret)
228      print_usage(argv[0]);
229
230   if ((options.query_mode || options.query_compare) && !options.nquery) {
231      fprintf(stderr, "query options only valid in query mode!\n");
232      print_usage(argv[0]);
233   }
234
235   script_finish();
236
237   if (interactive) {
238      pager_close();
239   }
240
241   return ret;
242}
243
244static void
245parse_addr(uint32_t *buf, int sz, unsigned int *len, uint64_t *gpuaddr)
246{
247   *gpuaddr = buf[0];
248   *len = buf[1];
249   if (sz > 8)
250      *gpuaddr |= ((uint64_t)(buf[2])) << 32;
251}
252
253static int
254handle_file(const char *filename, int start, int end, int draw)
255{
256   enum rd_sect_type type = RD_NONE;
257   void *buf = NULL;
258   struct io *io;
259   int submit = 0, got_gpu_id = 0;
260   int sz, ret = 0;
261   bool needs_reset = false;
262   bool skip = false;
263
264   options.draw_filter = draw;
265
266   cffdec_init(&options);
267
268   if (!options.unit_test)
269      printf("Reading %s...\n", filename);
270
271   script_start_cmdstream(filename);
272
273   if (!strcmp(filename, "-"))
274      io = io_openfd(0);
275   else
276      io = io_open(filename);
277
278   if (!io) {
279      fprintf(stderr, "could not open: %s\n", filename);
280      return -1;
281   }
282
283   struct {
284      unsigned int len;
285      uint64_t gpuaddr;
286   } gpuaddr = {0};
287
288   while (true) {
289      uint32_t arr[2];
290
291      ret = io_readn(io, arr, 8);
292      if (ret <= 0)
293         goto end;
294
295      while ((arr[0] == 0xffffffff) && (arr[1] == 0xffffffff)) {
296         ret = io_readn(io, arr, 8);
297         if (ret <= 0)
298            goto end;
299      }
300
301      type = arr[0];
302      sz = arr[1];
303
304      if (sz < 0) {
305         ret = -1;
306         goto end;
307      }
308
309      free(buf);
310
311      needs_wfi = false;
312
313      buf = malloc(sz + 1);
314      ((char *)buf)[sz] = '\0';
315      ret = io_readn(io, buf, sz);
316      if (ret < 0)
317         goto end;
318
319      switch (type) {
320      case RD_TEST:
321         printl(1, "test: %s\n", (char *)buf);
322         break;
323      case RD_CMD:
324         is_blob = true;
325         printl(2, "cmd: %s\n", (char *)buf);
326         skip = false;
327         if (exename) {
328            skip |= (strstr(buf, exename) != buf);
329         } else if (!show_comp) {
330            skip |= (strstr(buf, "fdperf") == buf);
331            skip |= (strstr(buf, "chrome") == buf);
332            skip |= (strstr(buf, "surfaceflinger") == buf);
333            skip |= ((char *)buf)[0] == 'X';
334         }
335         break;
336      case RD_VERT_SHADER:
337         printl(2, "vertex shader:\n%s\n", (char *)buf);
338         break;
339      case RD_FRAG_SHADER:
340         printl(2, "fragment shader:\n%s\n", (char *)buf);
341         break;
342      case RD_GPUADDR:
343         if (needs_reset) {
344            reset_buffers();
345            needs_reset = false;
346         }
347         parse_addr(buf, sz, &gpuaddr.len, &gpuaddr.gpuaddr);
348         break;
349      case RD_BUFFER_CONTENTS:
350         add_buffer(gpuaddr.gpuaddr, gpuaddr.len, buf);
351         buf = NULL;
352         break;
353      case RD_CMDSTREAM_ADDR:
354         if ((start <= submit) && (submit <= end)) {
355            unsigned int sizedwords;
356            uint64_t gpuaddr;
357            parse_addr(buf, sz, &sizedwords, &gpuaddr);
358            printl(2, "############################################################\n");
359            printl(2, "cmdstream: %d dwords\n", sizedwords);
360            if (!skip) {
361               script_start_submit();
362               dump_commands(hostptr(gpuaddr), sizedwords, 0);
363               script_end_submit();
364            }
365            printl(2, "############################################################\n");
366            printl(2, "vertices: %d\n", vertices);
367         }
368         needs_reset = true;
369         submit++;
370         break;
371      case RD_GPU_ID:
372         if (!got_gpu_id) {
373            uint32_t gpu_id = *((unsigned int *)buf);
374            if (!gpu_id)
375               break;
376            options.gpu_id = gpu_id;
377            printl(2, "gpu_id: %d\n", options.gpu_id);
378            cffdec_init(&options);
379            got_gpu_id = 1;
380         }
381         break;
382      case RD_CHIP_ID:
383         if (!got_gpu_id) {
384            uint64_t chip_id = *((uint64_t *)buf);
385            options.gpu_id = 100 * ((chip_id >> 24) & 0xff) +
386                  10 * ((chip_id >> 16) & 0xff) +
387                  ((chip_id >> 8) & 0xff);
388            printl(2, "gpu_id: %d\n", options.gpu_id);
389            cffdec_init(&options);
390            got_gpu_id = 1;
391         }
392         break;
393      default:
394         break;
395      }
396   }
397
398end:
399   script_end_cmdstream();
400
401   io_close(io);
402   fflush(stdout);
403
404   if (ret < 0) {
405      printf("corrupt file\n");
406   }
407   return 0;
408}
409