• 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