mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2026-05-20 19:58:19 +02:00
The start field in the Uniform USC word is only 8-bits, whereas 9-bits are required to address the entire uniform register file. This other word gets used for the high half, with start indexed from u128l in the natural way. Apparently spending the evening stuffing too many uniforms into Metal is paying off. Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/18813>
768 lines
24 KiB
C
768 lines
24 KiB
C
/*
|
|
* 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 <agx_pack.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <memory.h>
|
|
#include <stdbool.h>
|
|
#include <stdarg.h>
|
|
#include <ctype.h>
|
|
#include <sys/mman.h>
|
|
|
|
#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_mark_mapped(unsigned handle)
|
|
{
|
|
struct agx_bo *bo = agxdecode_find_handle(handle, AGX_ALLOC_REGULAR);
|
|
|
|
if (!bo) {
|
|
fprintf(stderr, "ERROR - unknown BO mapped with handle %u\n", handle);
|
|
return;
|
|
}
|
|
|
|
/* Mark mapped for future consumption */
|
|
bo->mapped = true;
|
|
}
|
|
|
|
static void
|
|
agxdecode_decode_segment_list(void *segment_list)
|
|
{
|
|
unsigned nr_handles = 0;
|
|
|
|
/* 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 = segment_list;
|
|
if (hdr->resource_group_count == 0) {
|
|
fprintf(agxdecode_dump_stream, "ERROR - empty map\n");
|
|
return;
|
|
}
|
|
|
|
if (hdr->segment_count != 1) {
|
|
fprintf(agxdecode_dump_stream, "ERROR - can't handle segment count %u\n",
|
|
hdr->segment_count);
|
|
}
|
|
|
|
fprintf(agxdecode_dump_stream, "Segment list:\n");
|
|
fprintf(agxdecode_dump_stream, " Command buffer shmem ID: %" PRIx64 "\n", hdr->cmdbuf_id);
|
|
fprintf(agxdecode_dump_stream, " Encoder ID: %" PRIx64 "\n", hdr->encoder_id);
|
|
fprintf(agxdecode_dump_stream, " Kernel commands start offset: %u\n",
|
|
hdr->kernel_commands_start_offset);
|
|
fprintf(agxdecode_dump_stream, " Kernel commands end offset: %u\n",
|
|
hdr->kernel_commands_end_offset);
|
|
fprintf(agxdecode_dump_stream, " Unknown: 0x%X\n", hdr->unk);
|
|
|
|
/* Expected structure: header followed by resource groups */
|
|
size_t length = sizeof(struct agx_map_header);
|
|
length += sizeof(struct agx_map_entry) * hdr->resource_group_count;
|
|
|
|
if (length != hdr->length) {
|
|
fprintf(agxdecode_dump_stream, "ERROR: expected length %zu, got %u\n",
|
|
length, hdr->length);
|
|
}
|
|
|
|
if (hdr->padding[0] || hdr->padding[1])
|
|
fprintf(agxdecode_dump_stream, "ERROR - padding tripped\n");
|
|
|
|
/* Check the entries */
|
|
struct agx_map_entry *groups = ((void *) hdr) + sizeof(*hdr);
|
|
for (unsigned i = 0; i < hdr->resource_group_count; ++i) {
|
|
struct agx_map_entry group = groups[i];
|
|
unsigned count = group.resource_count;
|
|
|
|
STATIC_ASSERT(ARRAY_SIZE(group.resource_id) == 6);
|
|
STATIC_ASSERT(ARRAY_SIZE(group.resource_unk) == 6);
|
|
STATIC_ASSERT(ARRAY_SIZE(group.resource_flags) == 6);
|
|
|
|
if ((count < 1) || (count > 6)) {
|
|
fprintf(agxdecode_dump_stream, "ERROR - invalid count %u\n", count);
|
|
continue;
|
|
}
|
|
|
|
for (unsigned j = 0; j < count; ++j) {
|
|
unsigned handle = group.resource_id[j];
|
|
unsigned unk = group.resource_unk[j];
|
|
unsigned flags = group.resource_flags[j];
|
|
|
|
if (!handle) {
|
|
fprintf(agxdecode_dump_stream, "ERROR - invalid handle %u\n", handle);
|
|
continue;
|
|
}
|
|
|
|
agxdecode_mark_mapped(handle);
|
|
nr_handles++;
|
|
|
|
fprintf(agxdecode_dump_stream, "%u (0x%X, 0x%X)\n", handle, unk, flags);
|
|
}
|
|
|
|
if (group.unka)
|
|
fprintf(agxdecode_dump_stream, "ERROR - unknown 0x%X\n", group.unka);
|
|
|
|
/* Visual separator for resource groups */
|
|
fprintf(agxdecode_dump_stream, "\n");
|
|
}
|
|
|
|
/* Check the handle count */
|
|
if (nr_handles != hdr->total_resources) {
|
|
fprintf(agxdecode_dump_stream, "ERROR - wrong handle count, got %u, expected %u (%u entries)\n",
|
|
nr_handles, hdr->total_resources, hdr->resource_group_count);
|
|
}
|
|
}
|
|
|
|
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, uint64_t *link, bool verbose);
|
|
|
|
#define STATE_DONE (0xFFFFFFFFu)
|
|
#define STATE_LINK (0xFFFFFFFEu)
|
|
|
|
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;
|
|
uint64_t link = 0;
|
|
|
|
if (verbose)
|
|
agxdecode_dump_bo(alloc, label);
|
|
fflush(agxdecode_dump_stream);
|
|
|
|
while (map < end) {
|
|
unsigned count = decoder(map, &link, 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;
|
|
} else if (count == STATE_LINK) {
|
|
alloc = agxdecode_find_mapped_gpu_mem_containing(link);
|
|
map = agxdecode_fetch_gpu_mem(link, 64);
|
|
end = (uint8_t *) alloc->ptr.cpu + alloc->size;
|
|
}
|
|
}
|
|
}
|
|
|
|
static unsigned
|
|
agxdecode_usc(const uint8_t *map, UNUSED uint64_t *link, UNUSED bool verbose)
|
|
{
|
|
enum agx_usc_control type = map[0];
|
|
|
|
#define USC_CASE(name, human) \
|
|
case AGX_USC_CONTROL_##name: { \
|
|
DUMP_CL(USC_##name, map, human); \
|
|
return AGX_USC_##name##_LENGTH; \
|
|
}
|
|
|
|
switch (type) {
|
|
case AGX_USC_CONTROL_NO_PRESHADER: {
|
|
DUMP_CL(USC_NO_PRESHADER, map, "No preshader");
|
|
return STATE_DONE;
|
|
}
|
|
|
|
case AGX_USC_CONTROL_PRESHADER: {
|
|
agx_unpack(agxdecode_dump_stream, map, USC_PRESHADER, ctrl);
|
|
DUMP_UNPACKED(USC_PRESHADER, ctrl, "Preshader\n");
|
|
|
|
agx_disassemble(agxdecode_fetch_gpu_mem(ctrl.code, 2048),
|
|
8192, agxdecode_dump_stream);
|
|
|
|
return STATE_DONE;
|
|
}
|
|
|
|
case AGX_USC_CONTROL_SHADER: {
|
|
agx_unpack(agxdecode_dump_stream, map, USC_SHADER, ctrl);
|
|
DUMP_UNPACKED(USC_SHADER, ctrl, "Shader\n");
|
|
|
|
agxdecode_log("\n");
|
|
agx_disassemble(agxdecode_fetch_gpu_mem(ctrl.code, 2048),
|
|
8192, agxdecode_dump_stream);
|
|
agxdecode_log("\n");
|
|
|
|
return AGX_USC_SHADER_LENGTH;
|
|
}
|
|
|
|
case AGX_USC_CONTROL_SAMPLER: {
|
|
agx_unpack(agxdecode_dump_stream, map, USC_SAMPLER, temp);
|
|
DUMP_UNPACKED(USC_SAMPLER, temp, "Sampler state\n");
|
|
|
|
uint8_t *samp = agxdecode_fetch_gpu_mem(temp.buffer,
|
|
AGX_SAMPLER_LENGTH * temp.count);
|
|
|
|
for (unsigned i = 0; i < temp.count; ++i) {
|
|
DUMP_CL(SAMPLER, samp, "Sampler");
|
|
samp += AGX_SAMPLER_LENGTH;
|
|
}
|
|
|
|
return AGX_USC_SAMPLER_LENGTH;
|
|
}
|
|
|
|
case AGX_USC_CONTROL_TEXTURE: {
|
|
agx_unpack(agxdecode_dump_stream, map, USC_TEXTURE, temp);
|
|
DUMP_UNPACKED(USC_TEXTURE, temp, "Texture state\n");
|
|
|
|
uint8_t *tex = agxdecode_fetch_gpu_mem(temp.buffer,
|
|
AGX_TEXTURE_LENGTH * temp.count);
|
|
|
|
/* Note: samplers only need 8 byte alignment? */
|
|
for (unsigned i = 0; i < temp.count; ++i) {
|
|
agx_unpack(agxdecode_dump_stream, tex, TEXTURE, t);
|
|
DUMP_CL(TEXTURE, tex, "Texture");
|
|
DUMP_CL(RENDER_TARGET, tex, "Render target");
|
|
|
|
tex += AGX_TEXTURE_LENGTH;
|
|
}
|
|
|
|
return AGX_USC_TEXTURE_LENGTH;
|
|
}
|
|
|
|
USC_CASE(FRAGMENT_PROPERTIES, "Fragment properties");
|
|
USC_CASE(UNIFORM, "Uniform");
|
|
USC_CASE(UNIFORM_HIGH, "Uniform high");
|
|
USC_CASE(SHARED, "Shared");
|
|
USC_CASE(REGISTERS, "Registers");
|
|
|
|
default:
|
|
fprintf(agxdecode_dump_stream, "Unknown USC control type: %u\n",
|
|
type);
|
|
hexdump(agxdecode_dump_stream, map, 8, false);
|
|
return 8;
|
|
}
|
|
|
|
#undef USC_CASE
|
|
}
|
|
|
|
#define PPP_PRINT(map, header_name, struct_name, human) \
|
|
if (hdr.header_name) { \
|
|
assert(((map + AGX_##struct_name##_LENGTH) <= (base + size)) && \
|
|
"buffer overrun in PPP update"); \
|
|
DUMP_CL(struct_name, map, human); \
|
|
map += AGX_##struct_name##_LENGTH; \
|
|
}
|
|
|
|
static void
|
|
agxdecode_record(uint64_t va, size_t size, bool verbose)
|
|
{
|
|
uint8_t *base = agxdecode_fetch_gpu_mem(va, size);
|
|
uint8_t *map = base;
|
|
|
|
agx_unpack(agxdecode_dump_stream, map, PPP_HEADER, hdr);
|
|
map += AGX_PPP_HEADER_LENGTH;
|
|
|
|
PPP_PRINT(map, fragment_control, FRAGMENT_CONTROL, "Fragment control");
|
|
PPP_PRINT(map, fragment_control_2, FRAGMENT_CONTROL_2, "Fragment control 2");
|
|
PPP_PRINT(map, fragment_front_face, FRAGMENT_FACE, "Front face");
|
|
PPP_PRINT(map, fragment_front_face_2, FRAGMENT_FACE_2, "Front face 2");
|
|
PPP_PRINT(map, fragment_front_stencil, FRAGMENT_STENCIL, "Front stencil");
|
|
PPP_PRINT(map, fragment_back_face, FRAGMENT_FACE, "Back face");
|
|
PPP_PRINT(map, fragment_back_face_2, FRAGMENT_FACE_2, "Back face 2");
|
|
PPP_PRINT(map, fragment_back_stencil, FRAGMENT_STENCIL, "Back stencil");
|
|
PPP_PRINT(map, depth_bias_scissor, DEPTH_BIAS_SCISSOR, "Depth bias/scissor");
|
|
PPP_PRINT(map, region_clip, REGION_CLIP, "Region clip");
|
|
PPP_PRINT(map, viewport, VIEWPORT, "Viewport");
|
|
PPP_PRINT(map, w_clamp, W_CLAMP, "W clamp");
|
|
PPP_PRINT(map, output_select, OUTPUT_SELECT, "Output select");
|
|
PPP_PRINT(map, varying_word_0, VARYING_0, "Varying word 0");
|
|
PPP_PRINT(map, varying_word_1, VARYING_1, "Varying word 1");
|
|
PPP_PRINT(map, cull, CULL, "Cull");
|
|
PPP_PRINT(map, cull_2, CULL_2, "Cull 2");
|
|
|
|
if (hdr.fragment_shader) {
|
|
agx_unpack(agxdecode_dump_stream, map, FRAGMENT_SHADER, frag);
|
|
agxdecode_stateful(frag.pipeline, "Fragment pipeline", agxdecode_usc, verbose);
|
|
|
|
if (frag.cf_bindings) {
|
|
uint8_t *cf = agxdecode_fetch_gpu_mem(frag.cf_bindings, 128);
|
|
hexdump(agxdecode_dump_stream, cf, 128, false);
|
|
|
|
DUMP_CL(CF_BINDING_HEADER, cf, "Coefficient binding header:");
|
|
cf += AGX_CF_BINDING_HEADER_LENGTH;
|
|
|
|
for (unsigned i = 0; i < frag.cf_binding_count; ++i) {
|
|
DUMP_CL(CF_BINDING, cf, "Coefficient binding:");
|
|
cf += AGX_CF_BINDING_LENGTH;
|
|
}
|
|
}
|
|
|
|
DUMP_UNPACKED(FRAGMENT_SHADER, frag, "Fragment shader\n");
|
|
map += AGX_FRAGMENT_SHADER_LENGTH;
|
|
}
|
|
|
|
PPP_PRINT(map, occlusion_query, FRAGMENT_OCCLUSION_QUERY, "Occlusion query");
|
|
PPP_PRINT(map, occlusion_query_2, FRAGMENT_OCCLUSION_QUERY_2, "Occlusion query 2");
|
|
PPP_PRINT(map, output_unknown, OUTPUT_UNKNOWN, "Output unknown");
|
|
PPP_PRINT(map, output_size, OUTPUT_SIZE, "Output size");
|
|
PPP_PRINT(map, varying_word_2, VARYING_2, "Varying word 2");
|
|
|
|
/* PPP print checks we don't read too much, now check we read enough */
|
|
assert(map == (base + size) && "invalid size of PPP update");
|
|
}
|
|
|
|
static unsigned
|
|
agxdecode_cdm(const uint8_t *map, uint64_t *link, bool verbose)
|
|
{
|
|
/* Bits 29-31 contain the block type */
|
|
enum agx_cdm_block_type block_type = (map[3] >> 5);
|
|
|
|
switch (block_type) {
|
|
case AGX_CDM_BLOCK_TYPE_COMPUTE_KERNEL: {
|
|
agx_unpack(agxdecode_dump_stream, map, LAUNCH, cmd);
|
|
agxdecode_stateful(cmd.pipeline, "Pipeline", agxdecode_usc, verbose);
|
|
DUMP_UNPACKED(LAUNCH, cmd, "Launch\n");
|
|
return AGX_LAUNCH_LENGTH;
|
|
}
|
|
|
|
case AGX_CDM_BLOCK_TYPE_STREAM_LINK: {
|
|
agx_unpack(agxdecode_dump_stream, map, CDM_STREAM_LINK, hdr);
|
|
DUMP_UNPACKED(CDM_STREAM_LINK, hdr, "Stream Link\n");
|
|
*link = hdr.target_lo | (((uint64_t) hdr.target_hi) << 32);
|
|
return STATE_LINK;
|
|
}
|
|
|
|
case AGX_CDM_BLOCK_TYPE_STREAM_TERMINATE: {
|
|
DUMP_CL(CDM_STREAM_TERMINATE, map, "Stream Terminate");
|
|
return STATE_DONE;
|
|
}
|
|
|
|
default:
|
|
fprintf(agxdecode_dump_stream, "Unknown CDM block type: %u\n",
|
|
block_type);
|
|
hexdump(agxdecode_dump_stream, map, 8, false);
|
|
return 8;
|
|
}
|
|
}
|
|
|
|
static unsigned
|
|
agxdecode_vdm(const uint8_t *map, uint64_t *link, bool verbose)
|
|
{
|
|
/* Bits 29-31 contain the block type */
|
|
enum agx_vdm_block_type block_type = (map[3] >> 5);
|
|
|
|
switch (block_type) {
|
|
case AGX_VDM_BLOCK_TYPE_PPP_STATE_UPDATE: {
|
|
agx_unpack(agxdecode_dump_stream, map, PPP_STATE, cmd);
|
|
|
|
uint64_t address = (((uint64_t) cmd.pointer_hi) << 32) | cmd.pointer_lo;
|
|
struct agx_bo *mem = agxdecode_find_mapped_gpu_mem_containing(address);
|
|
|
|
if (mem)
|
|
agxdecode_record(address, cmd.size_words * 4, verbose);
|
|
else
|
|
DUMP_UNPACKED(PPP_STATE, cmd, "Non-existant record (XXX)\n");
|
|
|
|
return AGX_PPP_STATE_LENGTH;
|
|
}
|
|
|
|
case AGX_VDM_BLOCK_TYPE_VDM_STATE_UPDATE: {
|
|
size_t length = AGX_VDM_STATE_LENGTH;
|
|
agx_unpack(agxdecode_dump_stream, map, VDM_STATE, hdr);
|
|
map += AGX_VDM_STATE_LENGTH;
|
|
|
|
#define VDM_PRINT(header_name, STRUCT_NAME, human) \
|
|
if (hdr.header_name##_present) { \
|
|
DUMP_CL(VDM_STATE_##STRUCT_NAME, map, human); \
|
|
map += AGX_VDM_STATE_##STRUCT_NAME##_LENGTH; \
|
|
length += AGX_VDM_STATE_##STRUCT_NAME##_LENGTH; \
|
|
}
|
|
|
|
VDM_PRINT(restart_index, RESTART_INDEX, "Restart index");
|
|
VDM_PRINT(vertex_shader_word_0, VERTEX_SHADER_WORD_0, "Vertex shader word 0");
|
|
|
|
if (hdr.vertex_shader_word_1_present) {
|
|
agx_unpack(agxdecode_dump_stream, map, VDM_STATE_VERTEX_SHADER_WORD_1,
|
|
word_1);
|
|
fprintf(agxdecode_dump_stream, "Pipeline %X\n", (uint32_t) word_1.pipeline);
|
|
agxdecode_stateful(word_1.pipeline, "Pipeline", agxdecode_usc, verbose);
|
|
}
|
|
|
|
VDM_PRINT(vertex_shader_word_1, VERTEX_SHADER_WORD_1, "Vertex shader word 1");
|
|
VDM_PRINT(vertex_outputs, VERTEX_OUTPUTS, "Vertex outputs");
|
|
VDM_PRINT(vertex_unknown, VERTEX_UNKNOWN, "Vertex unknown");
|
|
|
|
#undef VDM_PRINT
|
|
return ALIGN_POT(length, 8);
|
|
}
|
|
|
|
case AGX_VDM_BLOCK_TYPE_INDEX_LIST: {
|
|
size_t length = AGX_INDEX_LIST_LENGTH;
|
|
agx_unpack(agxdecode_dump_stream, map, INDEX_LIST, hdr);
|
|
DUMP_UNPACKED(INDEX_LIST, hdr, "Index List\n");
|
|
map += AGX_INDEX_LIST_LENGTH;
|
|
|
|
#define IDX_PRINT(header_name, STRUCT_NAME, human) \
|
|
if (hdr.header_name##_present) { \
|
|
DUMP_CL(INDEX_LIST_##STRUCT_NAME, map, human); \
|
|
map += AGX_INDEX_LIST_##STRUCT_NAME##_LENGTH; \
|
|
length += AGX_INDEX_LIST_##STRUCT_NAME##_LENGTH; \
|
|
}
|
|
|
|
IDX_PRINT(index_buffer, BUFFER_LO, "Index buffer");
|
|
IDX_PRINT(index_buffer_size, BUFFER_SIZE, "Index buffer size");
|
|
IDX_PRINT(index_count, COUNT, "Index count");
|
|
IDX_PRINT(instance_count, INSTANCES, "Instance count");
|
|
IDX_PRINT(start, START, "Start");
|
|
|
|
#undef IDX_PRINT
|
|
return ALIGN_POT(length, 8);
|
|
}
|
|
|
|
case AGX_VDM_BLOCK_TYPE_STREAM_LINK: {
|
|
agx_unpack(agxdecode_dump_stream, map, VDM_STREAM_LINK, hdr);
|
|
DUMP_UNPACKED(VDM_STREAM_LINK, hdr, "Stream Link\n");
|
|
*link = hdr.target_lo | (((uint64_t) hdr.target_hi) << 32);
|
|
return STATE_LINK;
|
|
}
|
|
|
|
case AGX_VDM_BLOCK_TYPE_STREAM_TERMINATE: {
|
|
DUMP_CL(VDM_STREAM_TERMINATE, map, "Stream Terminate");
|
|
return STATE_DONE;
|
|
}
|
|
|
|
default:
|
|
fprintf(agxdecode_dump_stream, "Unknown VDM block type: %u\n",
|
|
block_type);
|
|
hexdump(agxdecode_dump_stream, map, 8, false);
|
|
return 8;
|
|
}
|
|
}
|
|
|
|
static void
|
|
agxdecode_cs(uint32_t *cmdbuf, uint64_t encoder, bool verbose)
|
|
{
|
|
agx_unpack(agxdecode_dump_stream, cmdbuf + 16, IOGPU_COMPUTE, cs);
|
|
DUMP_UNPACKED(IOGPU_COMPUTE, cs, "Compute\n");
|
|
|
|
agxdecode_stateful(encoder, "Encoder", agxdecode_cdm, verbose);
|
|
}
|
|
|
|
static void
|
|
agxdecode_gfx(uint32_t *cmdbuf, uint64_t encoder, bool verbose)
|
|
{
|
|
agx_unpack(agxdecode_dump_stream, cmdbuf + 16, IOGPU_GRAPHICS, gfx);
|
|
DUMP_UNPACKED(IOGPU_GRAPHICS, gfx, "Graphics\n");
|
|
|
|
agxdecode_stateful(encoder, "Encoder", agxdecode_vdm, verbose);
|
|
|
|
if (gfx.clear_pipeline_unk) {
|
|
fprintf(agxdecode_dump_stream, "Unk: %X\n", gfx.clear_pipeline_unk);
|
|
agxdecode_stateful(gfx.clear_pipeline, "Clear pipeline",
|
|
agxdecode_usc, verbose);
|
|
}
|
|
|
|
if (gfx.store_pipeline_unk) {
|
|
assert(gfx.store_pipeline_unk == 0x4);
|
|
agxdecode_stateful(gfx.store_pipeline, "Store pipeline",
|
|
agxdecode_usc, verbose);
|
|
}
|
|
|
|
assert((gfx.partial_reload_pipeline_unk & 0xF) == 0x4);
|
|
if (gfx.partial_reload_pipeline) {
|
|
agxdecode_stateful(gfx.partial_reload_pipeline,
|
|
"Partial reload pipeline", agxdecode_usc, verbose);
|
|
}
|
|
|
|
if (gfx.partial_store_pipeline) {
|
|
agxdecode_stateful(gfx.partial_store_pipeline,
|
|
"Partial store pipeline", agxdecode_usc, verbose);
|
|
}
|
|
}
|
|
|
|
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");
|
|
|
|
/* Before decoding anything, validate the map. Set bo->mapped fields */
|
|
agxdecode_decode_segment_list(map->ptr.cpu);
|
|
|
|
/* Print the IOGPU stuff */
|
|
agx_unpack(agxdecode_dump_stream, cmdbuf->ptr.cpu, IOGPU_HEADER, cmd);
|
|
DUMP_UNPACKED(IOGPU_HEADER, cmd, "IOGPU Header\n");
|
|
|
|
DUMP_CL(IOGPU_ATTACHMENT_COUNT, ((uint8_t *) cmdbuf->ptr.cpu +
|
|
cmd.attachment_offset), "Attachment count");
|
|
|
|
uint32_t *attachments = (uint32_t *) ((uint8_t *) cmdbuf->ptr.cpu + cmd.attachment_offset);
|
|
unsigned attachment_count = attachments[3];
|
|
for (unsigned i = 0; i < attachment_count; ++i) {
|
|
uint32_t *ptr = attachments + 4 + (i * AGX_IOGPU_ATTACHMENT_LENGTH / 4);
|
|
DUMP_CL(IOGPU_ATTACHMENT, ptr, "Attachment");
|
|
}
|
|
|
|
if (cmd.unk_5 == 3)
|
|
agxdecode_cs((uint32_t *) cmdbuf->ptr.cpu, cmd.encoder, verbose);
|
|
else
|
|
agxdecode_gfx((uint32_t *) cmdbuf->ptr.cpu, cmd.encoder, verbose);
|
|
|
|
agxdecode_map_read_write();
|
|
}
|
|
|
|
void
|
|
agxdecode_dump_mappings(unsigned map_handle)
|
|
{
|
|
agxdecode_dump_file_open();
|
|
|
|
struct agx_bo *map = agxdecode_find_handle(map_handle, AGX_ALLOC_MEMMAP);
|
|
assert(map != NULL && "nonexistant mapping");
|
|
agxdecode_decode_segment_list(map->ptr.cpu);
|
|
|
|
for (unsigned i = 0; i < mmap_count; ++i) {
|
|
if (!mmap_array[i].ptr.cpu || !mmap_array[i].size || !mmap_array[i].mapped)
|
|
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);
|
|
|
|
for (unsigned i = 0; i < mmap_count; ++i) {
|
|
struct agx_bo *bo = &mmap_array[i];
|
|
bool match = (bo->handle == alloc->handle && bo->type == alloc->type);
|
|
assert(!match && "tried to alloc already allocated BO");
|
|
}
|
|
|
|
mmap_array[mmap_count++] = *alloc;
|
|
}
|
|
|
|
void
|
|
agxdecode_track_free(struct agx_bo *bo)
|
|
{
|
|
bool found = false;
|
|
|
|
for (unsigned i = 0; i < mmap_count; ++i) {
|
|
if (mmap_array[i].handle == bo->handle && mmap_array[i].type == bo->type) {
|
|
assert(!found && "mapped multiple times!");
|
|
found = true;
|
|
|
|
memset(&mmap_array[i], 0, sizeof(mmap_array[i]));
|
|
}
|
|
}
|
|
|
|
assert(found && "freed unmapped memory");
|
|
}
|
|
|
|
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("AGXDECODE_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();
|
|
}
|