• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017-2019 Alyssa Rosenzweig
3  * Copyright (C) 2017-2019 Connor Abbott
4  * Copyright (C) 2019 Collabora, Ltd.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice (including the next
14  * paragraph) shall be included in all copies or substantial portions of the
15  * Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23  * SOFTWARE.
24  */
25 
26 #include <agx_pack.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <memory.h>
30 #include <stdbool.h>
31 #include <stdarg.h>
32 #include <ctype.h>
33 #include <sys/mman.h>
34 
35 #include "decode.h"
36 #include "io.h"
37 #include "hexdump.h"
38 
39 static const char *agx_alloc_types[AGX_NUM_ALLOC] = { "mem", "map", "cmd" };
40 
41 static void
agx_disassemble(void * _code,size_t maxlen,FILE * fp)42 agx_disassemble(void *_code, size_t maxlen, FILE *fp)
43 {
44    /* stub */
45 }
46 
47 FILE *agxdecode_dump_stream;
48 
49 #define MAX_MAPPINGS 4096
50 
51 struct agx_bo mmap_array[MAX_MAPPINGS];
52 unsigned mmap_count = 0;
53 
54 struct agx_bo *ro_mappings[MAX_MAPPINGS];
55 unsigned ro_mapping_count = 0;
56 
57 static struct agx_bo *
agxdecode_find_mapped_gpu_mem_containing_rw(uint64_t addr)58 agxdecode_find_mapped_gpu_mem_containing_rw(uint64_t addr)
59 {
60    for (unsigned i = 0; i < mmap_count; ++i) {
61       if (mmap_array[i].type == AGX_ALLOC_REGULAR && addr >= mmap_array[i].ptr.gpu && (addr - mmap_array[i].ptr.gpu) < mmap_array[i].size)
62          return mmap_array + i;
63    }
64 
65    return NULL;
66 }
67 
68 static struct agx_bo *
agxdecode_find_mapped_gpu_mem_containing(uint64_t addr)69 agxdecode_find_mapped_gpu_mem_containing(uint64_t addr)
70 {
71    struct agx_bo *mem = agxdecode_find_mapped_gpu_mem_containing_rw(addr);
72 
73    if (mem && mem->ptr.cpu && !mem->ro) {
74       mprotect(mem->ptr.cpu, mem->size, PROT_READ);
75       mem->ro = true;
76       ro_mappings[ro_mapping_count++] = mem;
77       assert(ro_mapping_count < MAX_MAPPINGS);
78    }
79 
80    if (mem && !mem->mapped) {
81       fprintf(stderr, "[ERROR] access to memory not mapped (GPU %" PRIx64 ", handle %u)\n", mem->ptr.gpu, mem->handle);
82    }
83 
84    return mem;
85 }
86 
87 static struct agx_bo *
agxdecode_find_handle(unsigned handle,unsigned type)88 agxdecode_find_handle(unsigned handle, unsigned type)
89 {
90    for (unsigned i = 0; i < mmap_count; ++i) {
91       if (mmap_array[i].type != type)
92          continue;
93 
94       if (mmap_array[i].handle != handle)
95          continue;
96 
97       return &mmap_array[i];
98    }
99 
100    return NULL;
101 }
102 
103 static void
agxdecode_mark_mapped(unsigned handle)104 agxdecode_mark_mapped(unsigned handle)
105 {
106    struct agx_bo *bo = agxdecode_find_handle(handle, AGX_ALLOC_REGULAR);
107 
108    if (!bo) {
109       fprintf(stderr, "ERROR - unknown BO mapped with handle %u\n", handle);
110       return;
111    }
112 
113    /* Mark mapped for future consumption */
114    bo->mapped = true;
115 }
116 
117 static void
agxdecode_validate_map(void * map)118 agxdecode_validate_map(void *map)
119 {
120    unsigned nr_handles = 0;
121 
122    /* First, mark everything unmapped */
123    for (unsigned i = 0; i < mmap_count; ++i)
124       mmap_array[i].mapped = false;
125 
126    /* Check the header */
127    struct agx_map_header *hdr = map;
128    if (hdr->nr_entries == 0) {
129       fprintf(stderr, "ERROR - empty map\n");
130       return;
131    }
132 
133    for (unsigned i = 0; i < 6; ++i) {
134       unsigned handle = hdr->indices[i];
135       if (handle) {
136          agxdecode_mark_mapped(handle);
137          nr_handles++;
138       }
139    }
140 
141    /* Check the entries */
142    struct agx_map_entry *entries = (struct agx_map_entry *) (&hdr[1]);
143    for (unsigned i = 0; i < hdr->nr_entries - 1; ++i) {
144       struct agx_map_entry entry = entries[i];
145 
146       for (unsigned j = 0; j < 6; ++j) {
147          unsigned handle = entry.indices[j];
148          if (handle) {
149             agxdecode_mark_mapped(handle);
150             nr_handles++;
151          }
152       }
153    }
154 
155    /* Check the sentinel */
156    if (entries[hdr->nr_entries - 1].indices[0]) {
157       fprintf(stderr, "ERROR - last entry nonzero %u\n", entries[hdr->nr_entries - 1].indices[0]);
158       return;
159    }
160 
161    /* Check the handle count */
162    if (nr_handles != hdr->nr_handles) {
163       fprintf(stderr, "ERROR - wrong handle count, got %u, expected %u\n",
164             nr_handles, hdr->nr_handles);
165    }
166 }
167 
168 static inline void *
__agxdecode_fetch_gpu_mem(const struct agx_bo * mem,uint64_t gpu_va,size_t size,int line,const char * filename)169 __agxdecode_fetch_gpu_mem(const struct agx_bo *mem,
170                           uint64_t gpu_va, size_t size,
171                           int line, const char *filename)
172 {
173    if (!mem)
174       mem = agxdecode_find_mapped_gpu_mem_containing(gpu_va);
175 
176    if (!mem) {
177       fprintf(stderr, "Access to unknown memory %" PRIx64 " in %s:%d\n",
178               gpu_va, filename, line);
179       fflush(agxdecode_dump_stream);
180       assert(0);
181    }
182 
183    assert(mem);
184    assert(size + (gpu_va - mem->ptr.gpu) <= mem->size);
185 
186    return mem->ptr.cpu + gpu_va - mem->ptr.gpu;
187 }
188 
189 #define agxdecode_fetch_gpu_mem(gpu_va, size) \
190 	__agxdecode_fetch_gpu_mem(NULL, gpu_va, size, __LINE__, __FILE__)
191 
192 static void
agxdecode_map_read_write(void)193 agxdecode_map_read_write(void)
194 {
195    for (unsigned i = 0; i < ro_mapping_count; ++i) {
196       ro_mappings[i]->ro = false;
197       mprotect(ro_mappings[i]->ptr.cpu, ro_mappings[i]->size,
198                PROT_READ | PROT_WRITE);
199    }
200 
201    ro_mapping_count = 0;
202 }
203 
204 /* Helpers for parsing the cmdstream */
205 
206 #define DUMP_UNPACKED(T, var, str) { \
207         agxdecode_log(str); \
208         agx_print(agxdecode_dump_stream, T, var, (agxdecode_indent + 1) * 2); \
209 }
210 
211 #define DUMP_CL(T, cl, str) {\
212         agx_unpack(agxdecode_dump_stream, cl, T, temp); \
213         DUMP_UNPACKED(T, temp, str "\n"); \
214 }
215 
216 #define agxdecode_log(str) fputs(str, agxdecode_dump_stream)
217 #define agxdecode_msg(str) fprintf(agxdecode_dump_stream, "// %s", str)
218 
219 unsigned agxdecode_indent = 0;
220 uint64_t pipeline_base = 0;
221 
222 static void
agxdecode_dump_bo(struct agx_bo * bo,const char * name)223 agxdecode_dump_bo(struct agx_bo *bo, const char *name)
224 {
225    fprintf(agxdecode_dump_stream, "%s %s (%u)\n", name, bo->name ?: "", bo->handle);
226    hexdump(agxdecode_dump_stream, bo->ptr.cpu, bo->size, false);
227 }
228 
229 /* Abstraction for command stream parsing */
230 typedef unsigned (*decode_cmd)(const uint8_t *map, bool verbose);
231 
232 #define STATE_DONE (0xFFFFFFFFu)
233 
234 static void
agxdecode_stateful(uint64_t va,const char * label,decode_cmd decoder,bool verbose)235 agxdecode_stateful(uint64_t va, const char *label, decode_cmd decoder, bool verbose)
236 {
237    struct agx_bo *alloc = agxdecode_find_mapped_gpu_mem_containing(va);
238    assert(alloc != NULL && "nonexistant object");
239    fprintf(agxdecode_dump_stream, "%s (%" PRIx64 ", handle %u)\n", label, va, alloc->handle);
240    fflush(agxdecode_dump_stream);
241 
242    uint8_t *map = agxdecode_fetch_gpu_mem(va, 64);
243    uint8_t *end = (uint8_t *) alloc->ptr.cpu + alloc->size;
244 
245    if (verbose)
246       agxdecode_dump_bo(alloc, label);
247    fflush(agxdecode_dump_stream);
248 
249    while (map < end) {
250       unsigned count = decoder(map, verbose);
251 
252       /* If we fail to decode, default to a hexdump (don't hang) */
253       if (count == 0) {
254          hexdump(agxdecode_dump_stream, map, 8, false);
255          count = 8;
256       }
257 
258       map += count;
259       fflush(agxdecode_dump_stream);
260 
261       if (count == STATE_DONE)
262          break;
263    }
264 }
265 
266 unsigned COUNTER = 0;
267 static unsigned
agxdecode_pipeline(const uint8_t * map,UNUSED bool verbose)268 agxdecode_pipeline(const uint8_t *map, UNUSED bool verbose)
269 {
270    uint8_t zeroes[16] = { 0 };
271 
272    if (map[0] == 0x4D && map[1] == 0xbd) {
273       /* TODO: Disambiguation for extended is a guess */
274       agx_unpack(agxdecode_dump_stream, map, SET_SHADER_EXTENDED, cmd);
275       DUMP_UNPACKED(SET_SHADER_EXTENDED, cmd, "Set shader\n");
276 
277       if (cmd.preshader_mode == AGX_PRESHADER_MODE_PRESHADER) {
278          agxdecode_log("Preshader\n");
279          agx_disassemble(agxdecode_fetch_gpu_mem(cmd.preshader_code, 2048),
280                          2048, agxdecode_dump_stream);
281          agxdecode_log("\n---\n");
282       }
283 
284       agxdecode_log("\n");
285       agx_disassemble(agxdecode_fetch_gpu_mem(cmd.code, 2048),
286                       2048, agxdecode_dump_stream);
287       agxdecode_log("\n");
288 
289       char *name;
290       asprintf(&name, "file%u.bin", COUNTER++);
291       FILE *fp = fopen(name, "wb");
292       fwrite(agxdecode_fetch_gpu_mem(cmd.code, 2048), 1, 2048, fp);
293       fclose(fp);
294       free(name);
295       agxdecode_log("\n");
296 
297       return AGX_SET_SHADER_EXTENDED_LENGTH;
298    } else if (map[0] == 0x4D) {
299       agx_unpack(agxdecode_dump_stream, map, SET_SHADER, cmd);
300       DUMP_UNPACKED(SET_SHADER, cmd, "Set shader\n");
301       fflush(agxdecode_dump_stream);
302 
303       if (cmd.preshader_mode == AGX_PRESHADER_MODE_PRESHADER) {
304          agxdecode_log("Preshader\n");
305          agx_disassemble(agxdecode_fetch_gpu_mem(cmd.preshader_code, 2048),
306                          2048, agxdecode_dump_stream);
307          agxdecode_log("\n---\n");
308       }
309 
310       agxdecode_log("\n");
311       agx_disassemble(agxdecode_fetch_gpu_mem(cmd.code, 2048),
312                       2048, agxdecode_dump_stream);
313       char *name;
314       asprintf(&name, "file%u.bin", COUNTER++);
315       FILE *fp = fopen(name, "wb");
316       fwrite(agxdecode_fetch_gpu_mem(cmd.code, 2048), 1, 2048, fp);
317       fclose(fp);
318       free(name);
319       agxdecode_log("\n");
320 
321       return AGX_SET_SHADER_LENGTH;
322    } else if (map[0] == 0xDD) {
323       agx_unpack(agxdecode_dump_stream, map, BIND_TEXTURE, temp);
324       DUMP_UNPACKED(BIND_TEXTURE, temp, "Bind texture\n");
325 
326       uint8_t *tex = agxdecode_fetch_gpu_mem(temp.buffer, 64);
327       DUMP_CL(TEXTURE, tex, "Texture");
328       hexdump(agxdecode_dump_stream, tex + AGX_TEXTURE_LENGTH, 64 - AGX_TEXTURE_LENGTH, false);
329 
330       return AGX_BIND_TEXTURE_LENGTH;
331    } else if (map[0] == 0x9D) {
332       agx_unpack(agxdecode_dump_stream, map, BIND_SAMPLER, temp);
333       DUMP_UNPACKED(BIND_SAMPLER, temp, "Bind sampler\n");
334 
335       uint8_t *samp = agxdecode_fetch_gpu_mem(temp.buffer, 64);
336       DUMP_CL(SAMPLER, samp, "Sampler");
337       hexdump(agxdecode_dump_stream, samp + AGX_SAMPLER_LENGTH, 64 - AGX_SAMPLER_LENGTH, false);
338 
339       return AGX_BIND_SAMPLER_LENGTH;
340    } else if (map[0] == 0x1D) {
341       DUMP_CL(BIND_UNIFORM, map, "Bind uniform");
342       return AGX_BIND_UNIFORM_LENGTH;
343    } else if (memcmp(map, zeroes, 16) == 0) {
344       /* TODO: Termination */
345       return STATE_DONE;
346    } else {
347       return 0;
348    }
349 }
350 
351 static void
agxdecode_record(uint64_t va,size_t size,bool verbose)352 agxdecode_record(uint64_t va, size_t size, bool verbose)
353 {
354    uint8_t *map = agxdecode_fetch_gpu_mem(va, size);
355    uint32_t tag = 0;
356    memcpy(&tag, map, 4);
357 
358    if (tag == 0x00000C00) {
359       assert(size == AGX_VIEWPORT_LENGTH);
360       DUMP_CL(VIEWPORT, map, "Viewport");
361    } else if (tag == 0x0C020000) {
362       assert(size == AGX_LINKAGE_LENGTH);
363       DUMP_CL(LINKAGE, map, "Linkage");
364    } else if (tag == 0x10000b5) {
365       assert(size == AGX_RASTERIZER_LENGTH);
366       DUMP_CL(RASTERIZER, map, "Rasterizer");
367    } else if (tag == 0x200000) {
368       assert(size == AGX_CULL_LENGTH);
369       DUMP_CL(CULL, map, "Cull");
370    } else if (tag == 0x800000) {
371       assert(size == (AGX_BIND_PIPELINE_LENGTH + 4));
372 
373       agx_unpack(agxdecode_dump_stream, map, BIND_PIPELINE, cmd);
374       agxdecode_stateful(cmd.pipeline, "Pipeline", agxdecode_pipeline, verbose);
375 
376       /* TODO: parse */
377       if (cmd.fs_varyings) {
378          uint8_t *map = agxdecode_fetch_gpu_mem(cmd.fs_varyings, 128);
379          hexdump(agxdecode_dump_stream, map, 128, false);
380 
381          DUMP_CL(VARYING_HEADER, map, "Varying header:");
382          map += AGX_VARYING_HEADER_LENGTH;
383 
384          for (unsigned i = 0; i < cmd.input_count; ++i) {
385             DUMP_CL(VARYING, map, "Varying:");
386             map += AGX_VARYING_LENGTH;
387          }
388       }
389 
390       DUMP_UNPACKED(BIND_PIPELINE, cmd, "Bind fragment pipeline\n");
391    } else if (size == 0) {
392       pipeline_base = va;
393    } else {
394       fprintf(agxdecode_dump_stream, "Record %" PRIx64 "\n", va);
395       hexdump(agxdecode_dump_stream, map, size, false);
396    }
397 }
398 
399 static unsigned
agxdecode_cmd(const uint8_t * map,bool verbose)400 agxdecode_cmd(const uint8_t *map, bool verbose)
401 {
402    if (map[0] == 0x02 && map[1] == 0x10 && map[2] == 0x00 && map[3] == 0x00) {
403       agx_unpack(agxdecode_dump_stream, map, LAUNCH, cmd);
404       agxdecode_stateful(cmd.pipeline, "Pipeline", agxdecode_pipeline, verbose);
405       DUMP_UNPACKED(LAUNCH, cmd, "Launch\n");
406       return AGX_LAUNCH_LENGTH;
407    } else if (map[0] == 0x2E && map[1] == 0x00 && map[2] == 0x00 && map[3] == 0x40) {
408       agx_unpack(agxdecode_dump_stream, map, BIND_PIPELINE, cmd);
409       agxdecode_stateful(cmd.pipeline, "Pipeline", agxdecode_pipeline, verbose);
410       DUMP_UNPACKED(BIND_PIPELINE, cmd, "Bind vertex pipeline\n");
411 
412       /* Random unaligned null byte, it's pretty awful.. */
413       if (map[AGX_BIND_PIPELINE_LENGTH]) {
414          fprintf(agxdecode_dump_stream, "Unk unaligned %X\n",
415                map[AGX_BIND_PIPELINE_LENGTH]);
416       }
417 
418       return AGX_BIND_PIPELINE_LENGTH + 1;
419    } else if (map[1] == 0xc0 && map[2] == 0x61) {
420       DUMP_CL(DRAW, map - 1, "Draw");
421       return AGX_DRAW_LENGTH;
422    } else if (map[1] == 0x00 && map[2] == 0x00) {
423       /* No need to explicitly dump the record */
424       agx_unpack(agxdecode_dump_stream, map, RECORD, cmd);
425 
426       /* XXX: Why? */
427       if (pipeline_base && ((cmd.data >> 32) == 0)) {
428          cmd.data |= pipeline_base & 0xFF00000000ull;
429       }
430 
431       struct agx_bo *mem = agxdecode_find_mapped_gpu_mem_containing(cmd.data);
432 
433       if (mem)
434          agxdecode_record(cmd.data, cmd.size_words * 4, verbose);
435       else
436          DUMP_UNPACKED(RECORD, cmd, "Non-existant record (XXX)\n");
437 
438       return AGX_RECORD_LENGTH;
439    } else if (map[0] == 0 && map[1] == 0 && map[2] == 0xC0 && map[3] == 0x00) {
440       ASSERTED unsigned zero[4] = { 0 };
441       assert(memcmp(map + 4, zero, sizeof(zero)) == 0);
442       return STATE_DONE;
443    } else {
444       return 0;
445    }
446 }
447 
448 void
agxdecode_cmdstream(unsigned cmdbuf_handle,unsigned map_handle,bool verbose)449 agxdecode_cmdstream(unsigned cmdbuf_handle, unsigned map_handle, bool verbose)
450 {
451    agxdecode_dump_file_open();
452 
453    struct agx_bo *cmdbuf = agxdecode_find_handle(cmdbuf_handle, AGX_ALLOC_CMDBUF);
454    struct agx_bo *map = agxdecode_find_handle(map_handle, AGX_ALLOC_MEMMAP);
455    assert(cmdbuf != NULL && "nonexistant command buffer");
456    assert(map != NULL && "nonexistant mapping");
457 
458    if (verbose) {
459       agxdecode_dump_bo(cmdbuf, "Command buffer");
460       agxdecode_dump_bo(map, "Mapping");
461    }
462 
463    /* Before decoding anything, validate the map. Set bo->mapped fields */
464    agxdecode_validate_map(map->ptr.cpu);
465 
466    /* Print the IOGPU stuff */
467    agx_unpack(agxdecode_dump_stream, cmdbuf->ptr.cpu, IOGPU_HEADER, cmd);
468    DUMP_UNPACKED(IOGPU_HEADER, cmd, "IOGPU Header\n");
469    assert(cmd.attachment_offset_1 == cmd.attachment_offset_2);
470 
471    uint32_t *attachments = (uint32_t *) ((uint8_t *) cmdbuf->ptr.cpu + cmd.attachment_offset_1);
472    unsigned attachment_count = attachments[3];
473    for (unsigned i = 0; i < attachment_count; ++i) {
474       uint32_t *ptr = attachments + 4 + (i * AGX_IOGPU_ATTACHMENT_LENGTH / 4);
475       DUMP_CL(IOGPU_ATTACHMENT, ptr, "Attachment");
476    }
477 
478    /* TODO: What else is in here? */
479    uint64_t *encoder = ((uint64_t *) cmdbuf->ptr.cpu) + 7;
480    agxdecode_stateful(*encoder, "Encoder", agxdecode_cmd, verbose);
481 
482    uint64_t *clear_pipeline = ((uint64_t *) cmdbuf->ptr.cpu) + 79;
483    if (*clear_pipeline) {
484       assert(((*clear_pipeline) & 0xF) == 0x4);
485       agxdecode_stateful((*clear_pipeline) & ~0xF, "Clear pipeline",
486             agxdecode_pipeline, verbose);
487    }
488 
489    uint64_t *store_pipeline = ((uint64_t *) cmdbuf->ptr.cpu) + 82;
490    if (*store_pipeline) {
491       assert(((*store_pipeline) & 0xF) == 0x4);
492       agxdecode_stateful((*store_pipeline) & ~0xF, "Store pipeline",
493             agxdecode_pipeline, verbose);
494    }
495 
496    agxdecode_map_read_write();
497 }
498 
499 void
agxdecode_dump_mappings(unsigned map_handle)500 agxdecode_dump_mappings(unsigned map_handle)
501 {
502    agxdecode_dump_file_open();
503 
504    struct agx_bo *map = agxdecode_find_handle(map_handle, AGX_ALLOC_MEMMAP);
505    assert(map != NULL && "nonexistant mapping");
506    agxdecode_validate_map(map->ptr.cpu);
507 
508    for (unsigned i = 0; i < mmap_count; ++i) {
509       if (!mmap_array[i].ptr.cpu || !mmap_array[i].size || !mmap_array[i].mapped)
510          continue;
511 
512       assert(mmap_array[i].type < AGX_NUM_ALLOC);
513 
514       fprintf(agxdecode_dump_stream, "Buffer: type %s, gpu %" PRIx64 ", handle %u.bin:\n\n",
515               agx_alloc_types[mmap_array[i].type],
516               mmap_array[i].ptr.gpu, mmap_array[i].handle);
517 
518       hexdump(agxdecode_dump_stream, mmap_array[i].ptr.cpu, mmap_array[i].size, false);
519       fprintf(agxdecode_dump_stream, "\n");
520    }
521 }
522 
523 void
agxdecode_track_alloc(struct agx_bo * alloc)524 agxdecode_track_alloc(struct agx_bo *alloc)
525 {
526    assert((mmap_count + 1) < MAX_MAPPINGS);
527 
528    for (unsigned i = 0; i < mmap_count; ++i) {
529       struct agx_bo *bo = &mmap_array[i];
530       bool match = (bo->handle == alloc->handle && bo->type == alloc->type);
531       assert(!match && "tried to alloc already allocated BO");
532    }
533 
534    mmap_array[mmap_count++] = *alloc;
535 }
536 
537 void
agxdecode_track_free(struct agx_bo * bo)538 agxdecode_track_free(struct agx_bo *bo)
539 {
540    bool found = false;
541 
542    for (unsigned i = 0; i < mmap_count; ++i) {
543       if (mmap_array[i].handle == bo->handle && mmap_array[i].type == bo->type) {
544          assert(!found && "mapped multiple times!");
545          found = true;
546 
547          memset(&mmap_array[i], 0, sizeof(mmap_array[i]));
548       }
549    }
550 
551    assert(found && "freed unmapped memory");
552 }
553 
554 static int agxdecode_dump_frame_count = 0;
555 
556 void
agxdecode_dump_file_open(void)557 agxdecode_dump_file_open(void)
558 {
559    if (agxdecode_dump_stream)
560       return;
561 
562    /* This does a getenv every frame, so it is possible to use
563     * setenv to change the base at runtime.
564     */
565    const char *dump_file_base = getenv("PANDECODE_DUMP_FILE") ?: "agxdecode.dump";
566    if (!strcmp(dump_file_base, "stderr"))
567       agxdecode_dump_stream = stderr;
568    else {
569       char buffer[1024];
570       snprintf(buffer, sizeof(buffer), "%s.%04d", dump_file_base, agxdecode_dump_frame_count);
571       printf("agxdecode: dump command stream to file %s\n", buffer);
572       agxdecode_dump_stream = fopen(buffer, "w");
573       if (!agxdecode_dump_stream)
574          fprintf(stderr,
575                  "agxdecode: failed to open command stream log file %s\n",
576                  buffer);
577    }
578 }
579 
580 static void
agxdecode_dump_file_close(void)581 agxdecode_dump_file_close(void)
582 {
583    if (agxdecode_dump_stream && agxdecode_dump_stream != stderr) {
584       fclose(agxdecode_dump_stream);
585       agxdecode_dump_stream = NULL;
586    }
587 }
588 
589 void
agxdecode_next_frame(void)590 agxdecode_next_frame(void)
591 {
592    agxdecode_dump_file_close();
593    agxdecode_dump_frame_count++;
594 }
595 
596 void
agxdecode_close(void)597 agxdecode_close(void)
598 {
599    agxdecode_dump_file_close();
600 }
601