Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mesa
Path: blob/21.2-virgl/src/asahi/lib/decode.c
4560 views
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
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 *
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 *
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 *
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
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
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 *
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
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
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
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
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
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
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
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
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
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
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
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
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
590
agxdecode_next_frame(void)
591
{
592
agxdecode_dump_file_close();
593
agxdecode_dump_frame_count++;
594
}
595
596
void
597
agxdecode_close(void)
598
{
599
agxdecode_dump_file_close();
600
}
601
602