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