From a426abcb46ca360dbfc6035e7e8afc457f394bad Mon Sep 17 00:00:00 2001 From: Alyssa Rosenzweig Date: Sat, 24 Apr 2021 19:13:29 -0400 Subject: [PATCH] asahi: Add command buffer decode helpers Forked from Panfrost's pandecode. Like pandecode, most of the heavylifting is generated with GenXML, so this is relatively simple. Signed-off-by: Alyssa Rosenzweig Acked-by: Jason Ekstrand Acked-by: Bas Nieuwenhuizen Part-of: --- src/asahi/lib/{agx_allocation.h => agx_bo.h} | 0 src/asahi/lib/decode.c | 514 +++++++++++++++++++ src/asahi/lib/decode.h | 45 ++ src/asahi/lib/meson.build | 14 + src/asahi/meson.build | 5 + 5 files changed, 578 insertions(+) rename src/asahi/lib/{agx_allocation.h => agx_bo.h} (100%) create mode 100644 src/asahi/lib/decode.c create mode 100644 src/asahi/lib/decode.h diff --git a/src/asahi/lib/agx_allocation.h b/src/asahi/lib/agx_bo.h similarity index 100% rename from src/asahi/lib/agx_allocation.h rename to src/asahi/lib/agx_bo.h diff --git a/src/asahi/lib/decode.c b/src/asahi/lib/decode.c new file mode 100644 index 00000000000..4813ba990e1 --- /dev/null +++ b/src/asahi/lib/decode.c @@ -0,0 +1,514 @@ +/* + * Copyright (C) 2017-2019 Alyssa Rosenzweig + * Copyright (C) 2017-2019 Connor Abbott + * Copyright (C) 2019 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "decode.h" +#include "io.h" +#include "hexdump.h" + +static const char *agx_alloc_types[AGX_NUM_ALLOC] = { "mem", "map", "cmd" }; + +static void +agx_disassemble(void *_code, size_t maxlen, FILE *fp) +{ + /* stub */ +} + +FILE *agxdecode_dump_stream; + +#define MAX_MAPPINGS 4096 + +struct agx_bo mmap_array[MAX_MAPPINGS]; +unsigned mmap_count = 0; + +struct agx_bo *ro_mappings[MAX_MAPPINGS]; +unsigned ro_mapping_count = 0; + +static struct agx_bo * +agxdecode_find_mapped_gpu_mem_containing_rw(uint64_t addr) +{ + for (unsigned i = 0; i < mmap_count; ++i) { + if (mmap_array[i].type == AGX_ALLOC_REGULAR && addr >= mmap_array[i].ptr.gpu && (addr - mmap_array[i].ptr.gpu) < mmap_array[i].size) + return mmap_array + i; + } + + return NULL; +} + +static struct agx_bo * +agxdecode_find_mapped_gpu_mem_containing(uint64_t addr) +{ + struct agx_bo *mem = agxdecode_find_mapped_gpu_mem_containing_rw(addr); + + if (mem && mem->ptr.cpu && !mem->ro) { + mprotect(mem->ptr.cpu, mem->size, PROT_READ); + mem->ro = true; + ro_mappings[ro_mapping_count++] = mem; + assert(ro_mapping_count < MAX_MAPPINGS); + } + + if (mem && !mem->mapped) { + fprintf(stderr, "[ERROR] access to memory not mapped (GPU %" PRIx64 ", handle %u)\n", mem->ptr.gpu, mem->handle); + } + + return mem; +} + +static struct agx_bo * +agxdecode_find_handle(unsigned handle, unsigned type) +{ + for (unsigned i = 0; i < mmap_count; ++i) { + if (mmap_array[i].type != type) + continue; + + if (mmap_array[i].handle != handle) + continue; + + return &mmap_array[i]; + } + + return NULL; +} + +static void +agxdecode_validate_map(void *map) +{ + /* First, mark everything unmapped */ + for (unsigned i = 0; i < mmap_count; ++i) + mmap_array[i].mapped = false; + + /* Check the header */ + struct agx_map_header *hdr = map; + if (hdr->nr_entries_1 == 0) { + fprintf(stderr, "ERROR - empty map\n"); + return; + } + + if (hdr->nr_entries_1 != hdr->nr_entries_2) { + fprintf(stderr, "WARN - mismatched map %u vs %u\n", hdr->nr_entries_1, hdr->nr_entries_2); + } + + /* Check the entries */ + struct agx_map_entry *entries = (struct agx_map_entry *) (&hdr[1]); + for (unsigned i = 0; i < hdr->nr_entries_1 - 1; ++i) { + struct agx_map_entry entry = entries[i]; + struct agx_bo *bo = agxdecode_find_handle(entry.index, AGX_ALLOC_REGULAR); + + if (!bo) { + fprintf(stderr, "ERROR - unknown BO mapped with handle %u\n", entry.index); + continue; + } + + /* Mark mapped for future consumption */ + bo->mapped = true; + } + + /* Check the sentinel */ + if (entries[hdr->nr_entries_1 - 1].index) { + fprintf(stderr, "ERROR - last entry nonzero %u\n", entries[hdr->nr_entries_1 - 1].index); + return; + } +} + +static inline void * +__agxdecode_fetch_gpu_mem(const struct agx_bo *mem, + uint64_t gpu_va, size_t size, + int line, const char *filename) +{ + if (!mem) + mem = agxdecode_find_mapped_gpu_mem_containing(gpu_va); + + if (!mem) { + fprintf(stderr, "Access to unknown memory %" PRIx64 " in %s:%d\n", + gpu_va, filename, line); + fflush(agxdecode_dump_stream); + assert(0); + } + + assert(mem); + assert(size + (gpu_va - mem->ptr.gpu) <= mem->size); + + return mem->ptr.cpu + gpu_va - mem->ptr.gpu; +} + +#define agxdecode_fetch_gpu_mem(gpu_va, size) \ + __agxdecode_fetch_gpu_mem(NULL, gpu_va, size, __LINE__, __FILE__) + +static void +agxdecode_map_read_write(void) +{ + for (unsigned i = 0; i < ro_mapping_count; ++i) { + ro_mappings[i]->ro = false; + mprotect(ro_mappings[i]->ptr.cpu, ro_mappings[i]->size, + PROT_READ | PROT_WRITE); + } + + ro_mapping_count = 0; +} + +/* Helpers for parsing the cmdstream */ + +#define DUMP_UNPACKED(T, var, str) { \ + agxdecode_log(str); \ + agx_print(agxdecode_dump_stream, T, var, (agxdecode_indent + 1) * 2); \ +} + +#define DUMP_CL(T, cl, str) {\ + agx_unpack(agxdecode_dump_stream, cl, T, temp); \ + DUMP_UNPACKED(T, temp, str "\n"); \ +} + +#define agxdecode_log(str) fputs(str, agxdecode_dump_stream) +#define agxdecode_msg(str) fprintf(agxdecode_dump_stream, "// %s", str) + +unsigned agxdecode_indent = 0; + +static void +agxdecode_dump_bo(struct agx_bo *bo, const char *name) +{ + fprintf(agxdecode_dump_stream, "%s %s (%u)\n", name, bo->name ?: "", bo->handle); + hexdump(agxdecode_dump_stream, bo->ptr.cpu, bo->size, false); +} + +/* Abstraction for command stream parsing */ +typedef unsigned (*decode_cmd)(const uint8_t *map, bool verbose); + +#define STATE_DONE (0xFFFFFFFFu) + +static void +agxdecode_stateful(uint64_t va, const char *label, decode_cmd decoder, bool verbose) +{ + struct agx_bo *alloc = agxdecode_find_mapped_gpu_mem_containing(va); + assert(alloc != NULL && "nonexistant object"); + fprintf(agxdecode_dump_stream, "%s (%" PRIx64 ", handle %u)\n", label, va, alloc->handle); + fflush(agxdecode_dump_stream); + + uint8_t *map = agxdecode_fetch_gpu_mem(va, 64); + uint8_t *end = (uint8_t *) alloc->ptr.cpu + alloc->size; + + if (verbose) + agxdecode_dump_bo(alloc, label); + fflush(agxdecode_dump_stream); + + while (map < end) { + unsigned count = decoder(map, verbose); + + /* If we fail to decode, default to a hexdump (don't hang) */ + if (count == 0) { + hexdump(agxdecode_dump_stream, map, 8, false); + count = 8; + } + + map += count; + fflush(agxdecode_dump_stream); + + if (count == STATE_DONE) + break; + } +} + +unsigned COUNTER = 0; +static unsigned +agxdecode_pipeline(const uint8_t *map, UNUSED bool verbose) +{ + uint8_t zeroes[16] = { 0 }; + + if (map[0] == 0x4D && map[1] == 0xbd) { + /* TODO: Disambiguation for extended is a guess */ + agx_unpack(agxdecode_dump_stream, map, SET_SHADER_EXTENDED, cmd); + DUMP_UNPACKED(SET_SHADER_EXTENDED, cmd, "Set shader\n"); + + if (cmd.preshader_mode == AGX_PRESHADER_MODE_PRESHADER) { + agxdecode_log("Preshader\n"); + agx_disassemble(agxdecode_fetch_gpu_mem(cmd.preshader_code, 2048), + 2048, agxdecode_dump_stream); + agxdecode_log("\n---\n"); + } + + agxdecode_log("\n"); + agx_disassemble(agxdecode_fetch_gpu_mem(cmd.code, 2048), + 2048, agxdecode_dump_stream); + agxdecode_log("\n"); + + char *name; + asprintf(&name, "file%u.bin", COUNTER++); + FILE *fp = fopen(name, "wb"); + fwrite(agxdecode_fetch_gpu_mem(cmd.code, 2048), 1, 2048, fp); + fclose(fp); + free(name); + agxdecode_log("\n"); + + return AGX_SET_SHADER_EXTENDED_LENGTH; + } else if (map[0] == 0x4D) { + agx_unpack(agxdecode_dump_stream, map, SET_SHADER, cmd); + DUMP_UNPACKED(SET_SHADER, cmd, "Set shader\n"); + fflush(agxdecode_dump_stream); + + if (cmd.preshader_mode == AGX_PRESHADER_MODE_PRESHADER) { + agxdecode_log("Preshader\n"); + agx_disassemble(agxdecode_fetch_gpu_mem(cmd.preshader_code, 2048), + 2048, agxdecode_dump_stream); + agxdecode_log("\n---\n"); + } + + agxdecode_log("\n"); + agx_disassemble(agxdecode_fetch_gpu_mem(cmd.code, 2048), + 2048, agxdecode_dump_stream); + char *name; + asprintf(&name, "file%u.bin", COUNTER++); + FILE *fp = fopen(name, "wb"); + fwrite(agxdecode_fetch_gpu_mem(cmd.code, 2048), 1, 2048, fp); + fclose(fp); + free(name); + agxdecode_log("\n"); + + return AGX_SET_SHADER_LENGTH; + } else if (map[0] == 0xDD) { + agx_unpack(agxdecode_dump_stream, map, BIND_TEXTURE, temp); + DUMP_UNPACKED(BIND_TEXTURE, temp, "Bind texture\n"); + + uint8_t *tex = agxdecode_fetch_gpu_mem(temp.buffer, 64); + DUMP_CL(TEXTURE, tex, "Texture"); + hexdump(agxdecode_dump_stream, tex + AGX_TEXTURE_LENGTH, 64 - AGX_TEXTURE_LENGTH, false); + + return AGX_BIND_TEXTURE_LENGTH; + } else if (map[0] == 0x9D) { + agx_unpack(agxdecode_dump_stream, map, BIND_SAMPLER, temp); + DUMP_UNPACKED(BIND_SAMPLER, temp, "Bind sampler\n"); + + uint8_t *samp = agxdecode_fetch_gpu_mem(temp.buffer, 64); + DUMP_CL(SAMPLER, samp, "Sampler"); + hexdump(agxdecode_dump_stream, samp + AGX_SAMPLER_LENGTH, 64 - AGX_SAMPLER_LENGTH, false); + + return AGX_BIND_SAMPLER_LENGTH; + } else if (map[0] == 0x1D) { + DUMP_CL(BIND_UNIFORM, map, "Bind uniform"); + return AGX_BIND_UNIFORM_LENGTH; + } else if (memcmp(map, zeroes, 16) == 0) { + /* TODO: Termination */ + return STATE_DONE; + } else { + return 0; + } +} + +static void +agxdecode_record(uint64_t va, size_t size, bool verbose) +{ + uint8_t *map = agxdecode_fetch_gpu_mem(va, size); + uint32_t tag = 0; + memcpy(&tag, map, 4); + + if (tag == 0x00000C00) { + assert(size == AGX_VIEWPORT_LENGTH); + DUMP_CL(VIEWPORT, map, "Viewport"); + } else if (tag == 0x0C020000) { + assert(size == AGX_LINKAGE_LENGTH); + DUMP_CL(LINKAGE, map, "Linkage"); + } else if (tag == 0x10000b5) { + assert(size == AGX_RASTERIZER_LENGTH); + DUMP_CL(RASTERIZER, map, "Rasterizer"); + } else if (tag == 0x800000) { + assert(size == (AGX_BIND_PIPELINE_LENGTH + 4)); + + agx_unpack(agxdecode_dump_stream, map, BIND_PIPELINE, cmd); + agxdecode_stateful(cmd.pipeline, "Pipeline", agxdecode_pipeline, verbose); + + /* TODO: parse */ + if (cmd.fs_varyings) { + uint8_t *map = agxdecode_fetch_gpu_mem(cmd.fs_varyings, 128); + hexdump(agxdecode_dump_stream, map, 128, false); + } + + DUMP_UNPACKED(BIND_PIPELINE, cmd, "Bind fragment pipeline\n"); + } else { + fprintf(agxdecode_dump_stream, "Record %" PRIx64 "\n", va); + hexdump(agxdecode_dump_stream, map, size, false); + } +} + +static unsigned +agxdecode_cmd(const uint8_t *map, bool verbose) +{ + if (map[0] == 0x02 && map[1] == 0x10 && map[2] == 0x00 && map[3] == 0x00) { + agx_unpack(agxdecode_dump_stream, map, LAUNCH, cmd); + agxdecode_stateful(cmd.pipeline, "Pipeline", agxdecode_pipeline, verbose); + DUMP_UNPACKED(LAUNCH, cmd, "Launch\n"); + return AGX_LAUNCH_LENGTH; + } else if (map[0] == 0x2E && map[1] == 0x00 && map[2] == 0x00 && map[3] == 0x40) { + agx_unpack(agxdecode_dump_stream, map, BIND_PIPELINE, cmd); + agxdecode_stateful(cmd.pipeline, "Pipeline", agxdecode_pipeline, verbose); + DUMP_UNPACKED(BIND_PIPELINE, cmd, "Bind vertex pipeline\n"); + + /* Random unaligned null byte, it's pretty awful.. */ + if (map[AGX_BIND_PIPELINE_LENGTH]) { + fprintf(agxdecode_dump_stream, "Unk unaligned %X\n", + map[AGX_BIND_PIPELINE_LENGTH]); + } + + return AGX_BIND_PIPELINE_LENGTH + 1; + } else if (map[1] == 0xc0 && map[2] == 0x61) { + DUMP_CL(DRAW, map, "Draw"); + return AGX_DRAW_LENGTH; + } else if (map[1] == 0x00 && map[2] == 0x00) { + /* No need to explicitly dump the record */ + agx_unpack(agxdecode_dump_stream, map, RECORD, cmd); + struct agx_bo *mem = agxdecode_find_mapped_gpu_mem_containing(cmd.data); + + if (mem) + agxdecode_record(cmd.data, cmd.size_words * 4, verbose); + else + DUMP_UNPACKED(RECORD, cmd, "Non-existant record (XXX)\n"); + + return AGX_RECORD_LENGTH; + } else if (map[0] == 0 && map[1] == 0 && map[2] == 0xC0 && map[3] == 0x00) { + ASSERTED unsigned zero[16] = { 0 }; + assert(memcmp(map + 4, zero, sizeof(zero)) == 0); + return STATE_DONE; + } else { + return 0; + } +} + +void +agxdecode_cmdstream(unsigned cmdbuf_handle, unsigned map_handle, bool verbose) +{ + agxdecode_dump_file_open(); + + struct agx_bo *cmdbuf = agxdecode_find_handle(cmdbuf_handle, AGX_ALLOC_CMDBUF); + struct agx_bo *map = agxdecode_find_handle(map_handle, AGX_ALLOC_MEMMAP); + assert(cmdbuf != NULL && "nonexistant command buffer"); + assert(map != NULL && "nonexistant mapping"); + + if (verbose) { + agxdecode_dump_bo(cmdbuf, "Command buffer"); + agxdecode_dump_bo(map, "Mapping"); + } + + /* Before decoding anything, validate the map. Set bo->mapped fields */ + agxdecode_validate_map(map->ptr.cpu); + + /* TODO: What else is in here? */ + uint64_t *encoder = ((uint64_t *) cmdbuf->ptr.cpu) + 7; + agxdecode_stateful(*encoder, "Encoder", agxdecode_cmd, verbose); + + agxdecode_map_read_write(); +} + +void +agxdecode_dump_mappings(void) +{ + agxdecode_dump_file_open(); + + for (unsigned i = 0; i < mmap_count; ++i) { + if (!mmap_array[i].ptr.cpu || !mmap_array[i].size) + continue; + + assert(mmap_array[i].type < AGX_NUM_ALLOC); + + fprintf(agxdecode_dump_stream, "Buffer: type %s, gpu %" PRIx64 ", handle %u.bin:\n\n", + agx_alloc_types[mmap_array[i].type], + mmap_array[i].ptr.gpu, mmap_array[i].handle); + + hexdump(agxdecode_dump_stream, mmap_array[i].ptr.cpu, mmap_array[i].size, false); + fprintf(agxdecode_dump_stream, "\n"); + } +} + +void +agxdecode_track_alloc(struct agx_bo *alloc) +{ + assert((mmap_count + 1) < MAX_MAPPINGS); + mmap_array[mmap_count++] = *alloc; +} + +void +agxdecode_track_free(struct agx_bo *bo) +{ + for (unsigned i = 0; i < mmap_count; ++i) { + if (mmap_array[i].handle == bo->handle && mmap_array[i].type == bo->type) { + mmap_array[i].ptr.cpu = 0; + mmap_array[i].ptr.gpu = 0; + mmap_array[i].size = 0; + break; + } + } +} + +static int agxdecode_dump_frame_count = 0; + +void +agxdecode_dump_file_open(void) +{ + if (agxdecode_dump_stream) + return; + + /* This does a getenv every frame, so it is possible to use + * setenv to change the base at runtime. + */ + const char *dump_file_base = getenv("PANDECODE_DUMP_FILE") ?: "agxdecode.dump"; + if (!strcmp(dump_file_base, "stderr")) + agxdecode_dump_stream = stderr; + else { + char buffer[1024]; + snprintf(buffer, sizeof(buffer), "%s.%04d", dump_file_base, agxdecode_dump_frame_count); + printf("agxdecode: dump command stream to file %s\n", buffer); + agxdecode_dump_stream = fopen(buffer, "w"); + if (!agxdecode_dump_stream) + fprintf(stderr, + "agxdecode: failed to open command stream log file %s\n", + buffer); + } +} + +static void +agxdecode_dump_file_close(void) +{ + if (agxdecode_dump_stream && agxdecode_dump_stream != stderr) { + fclose(agxdecode_dump_stream); + agxdecode_dump_stream = NULL; + } +} + +void +agxdecode_next_frame(void) +{ + agxdecode_dump_file_close(); + agxdecode_dump_frame_count++; +} + +void +agxdecode_close(void) +{ + agxdecode_dump_file_close(); +} diff --git a/src/asahi/lib/decode.h b/src/asahi/lib/decode.h new file mode 100644 index 00000000000..836bbfc3a0f --- /dev/null +++ b/src/asahi/lib/decode.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017-2019 Lyude Paul + * Copyright (C) 2017-2019 Alyssa Rosenzweig + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#ifndef __AGX_DECODE_H__ +#define __AGX_DECODE_H__ + +#include "agx_bo.h" + +void agxdecode_next_frame(void); + +void agxdecode_close(void); + +void agxdecode_cmdstream(unsigned cmdbuf_index, unsigned map_index, bool verbose); + +void agxdecode_dump_file_open(void); + +void agxdecode_track_alloc(struct agx_bo *alloc); + +void agxdecode_dump_mappings(void); + +void agxdecode_track_free(struct agx_bo *bo); + +#endif /* __MMAP_TRACE_H__ */ diff --git a/src/asahi/lib/meson.build b/src/asahi/lib/meson.build index 7bde6afad84..f92391e2fb7 100644 --- a/src/asahi/lib/meson.build +++ b/src/asahi/lib/meson.build @@ -19,6 +19,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +libasahi_decode_files = files( + 'decode.c', +) + agx_pack = custom_target( 'agx_pack.h', input : ['gen_pack.py', 'cmdbuf.xml'], @@ -31,3 +35,13 @@ idep_agx_pack = declare_dependency( sources : [agx_pack], include_directories : include_directories('.'), ) + +libasahi_decode = static_library( + 'asahi_decode', + [libasahi_decode_files, agx_pack], + include_directories : [inc_include, inc_src, inc_mapi, inc_mesa, inc_gallium, inc_gallium_aux, inc_asahi], + dependencies : idep_mesautil, + c_args : [no_override_init_args], + gnu_symbol_visibility : 'hidden', + build_by_default : false, +) diff --git a/src/asahi/meson.build b/src/asahi/meson.build index 39f7a78c712..fc7823c5b4e 100644 --- a/src/asahi/meson.build +++ b/src/asahi/meson.build @@ -19,7 +19,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +inc_asahi = include_directories([ + '.', 'lib', 'compiler' +]) + subdir('compiler') +subdir('lib') files_agx = files( 'compiler/cmdline.c',