From f9a234ef0419039c7f8f037d1366b9d5dccba2c1 Mon Sep 17 00:00:00 2001 From: Matt Coster Date: Wed, 29 Jun 2022 09:47:33 +0100 Subject: [PATCH] pvr: debug: Add "cs" debug option to dump control stream on job submit With PVR_DEBUG=cs, the control stream will be dumped to stderr immediately prior to every render or compute job submission. Signed-off-by: Matt Coster Reviewed-by: Karmjit Mahil Part-of: --- src/imagination/common/meson.build | 2 + src/imagination/common/pvr_debug.c | 8 +- src/imagination/common/pvr_debug.h | 8 +- src/imagination/common/pvr_dump.c | 276 ++++ src/imagination/common/pvr_dump.h | 594 +++++++++ src/imagination/common/pvr_util.h | 9 + src/imagination/include/pvr_types.h | 4 + src/imagination/vulkan/meson.build | 2 + src/imagination/vulkan/pvr_bo.c | 53 + src/imagination/vulkan/pvr_bo.h | 5 + src/imagination/vulkan/pvr_csb.c | 16 +- src/imagination/vulkan/pvr_csb.h | 9 + src/imagination/vulkan/pvr_csb_enum_helpers.h | 15 + src/imagination/vulkan/pvr_dump_bo.c | 82 ++ src/imagination/vulkan/pvr_dump_bo.h | 50 + src/imagination/vulkan/pvr_dump_csb.c | 1108 +++++++++++++++++ src/imagination/vulkan/pvr_job_compute.c | 7 + src/imagination/vulkan/pvr_job_render.c | 15 + 18 files changed, 2250 insertions(+), 13 deletions(-) create mode 100644 src/imagination/common/pvr_dump.c create mode 100644 src/imagination/common/pvr_dump.h create mode 100644 src/imagination/vulkan/pvr_dump_bo.c create mode 100644 src/imagination/vulkan/pvr_dump_bo.h create mode 100644 src/imagination/vulkan/pvr_dump_csb.c diff --git a/src/imagination/common/meson.build b/src/imagination/common/meson.build index 14420d1bb9d..954ec49b80d 100644 --- a/src/imagination/common/meson.build +++ b/src/imagination/common/meson.build @@ -24,11 +24,13 @@ libpowervr_common = static_library( [ 'pvr_debug.c', 'pvr_device_info.c', + 'pvr_dump.c', 'pvr_util.c', ], include_directories : [ inc_include, inc_src, + inc_imagination, ], c_args : [no_override_init_args], gnu_symbol_visibility : 'hidden', diff --git a/src/imagination/common/pvr_debug.c b/src/imagination/common/pvr_debug.c index 94f235453bb..2d56a12282a 100644 --- a/src/imagination/common/pvr_debug.c +++ b/src/imagination/common/pvr_debug.c @@ -28,13 +28,13 @@ uint32_t PVR_DEBUG = 0; +/* clang-format off */ static const struct debug_named_value debug_control[] = { - /* Define debug values here in the same order as in "pvr_debug.h". Example: - { "some_option", PVR_DEBUG_SOME_OPTION, - "This is a description for some option" }, - */ + { "cs", PVR_DEBUG_DUMP_CONTROL_STREAM, + "Dump the contents of the control stream buffer on every job submit." }, DEBUG_NAMED_VALUE_END }; +/* clang-format on */ DEBUG_GET_ONCE_FLAGS_OPTION(pvr_debug, "PVR_DEBUG", debug_control, 0) diff --git a/src/imagination/common/pvr_debug.h b/src/imagination/common/pvr_debug.h index 7c92f630bf9..53dfb679eaa 100644 --- a/src/imagination/common/pvr_debug.h +++ b/src/imagination/common/pvr_debug.h @@ -30,9 +30,11 @@ extern uint32_t PVR_DEBUG; -/* Define debug values here. Example: -#define PVR_DEBUG_SOME_OPTION BITFIELD_BIT(0) - */ +/* clang-format off */ +#define PVR_IS_DEBUG_SET(x) unlikely(PVR_DEBUG & PVR_DEBUG_##x) +/* clang-format on */ + +#define PVR_DEBUG_DUMP_CONTROL_STREAM BITFIELD_BIT(0) void pvr_process_debug_variable(void); diff --git a/src/imagination/common/pvr_dump.c b/src/imagination/common/pvr_dump.c new file mode 100644 index 00000000000..2ccc71e2bda --- /dev/null +++ b/src/imagination/common/pvr_dump.c @@ -0,0 +1,276 @@ +/* + * Copyright © 2022 Imagination Technologies 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 "pvr_dump.h" +#include "pvr_util.h" + +const struct pvr_dump_ctx __pvr_dump_ctx_invalid = { + .active_child = &__pvr_dump_ctx_invalid, +}; + +/***************************************************************************** + Hex dumps +*****************************************************************************/ + +#define HEX_WORD_SIZE ((unsigned)sizeof(uint32_t)) +#define HEX_WORD_FMT "%08" PRIx32 + +/* This must be even, and should probably always be a power of 2. */ +#define HEX_LINE_SIZE UINT32_C(8) + +struct pvr_dump_hex_ctx { + struct pvr_dump_ctx base; + + const uint32_t *start_ptr; + const uint32_t *end_ptr; + + uint64_t nr_words; + uint32_t offset_digits; + + /* User-modifiable values */ + const uint32_t *line_ptr; + + uint32_t prev_non_zero_trailing_zero_words; + uint64_t prev_non_zero_leading_zero_lines; + const uint32_t *prev_non_zero_line; + uint64_t zero_lines; +}; + +static bool pvr_dump_hex_ctx_push(struct pvr_dump_hex_ctx *const ctx, + struct pvr_dump_buffer_ctx *const parent_ctx, + const uint64_t nr_words) +{ + const uint64_t real_nr_words = + nr_words ? nr_words : parent_ctx->remaining_size / HEX_WORD_SIZE; + const uint64_t nr_bytes = real_nr_words * HEX_WORD_SIZE; + bool ret; + + if (parent_ctx->remaining_size < nr_bytes || + (!nr_words && nr_bytes != parent_ctx->remaining_size) || + !ptr_is_aligned(parent_ctx->ptr, HEX_WORD_SIZE)) { + return false; + } + + ret = pvr_dump_ctx_push(&ctx->base, &parent_ctx->base); + if (!ret) + return false; + + ctx->start_ptr = parent_ctx->ptr; + ctx->end_ptr = ctx->start_ptr + real_nr_words; + ctx->nr_words = real_nr_words; + ctx->offset_digits = u64_hex_digits(nr_bytes); + + ctx->line_ptr = ctx->start_ptr; + + ctx->prev_non_zero_trailing_zero_words = 0; + ctx->prev_non_zero_leading_zero_lines = 0; + ctx->prev_non_zero_line = NULL; + ctx->zero_lines = 0; + + return true; +} + +static struct pvr_dump_buffer_ctx * +pvr_dump_hex_ctx_pop(struct pvr_dump_hex_ctx *const ctx) +{ + struct pvr_dump_buffer_ctx *parent; + struct pvr_dump_ctx *parent_base; + + if (ctx->line_ptr != ctx->end_ptr) { + ctx->base.ok = false; + return NULL; + } + + parent_base = pvr_dump_ctx_pop(&ctx->base); + if (!parent_base) + return NULL; + + parent = container_of(parent_base, struct pvr_dump_buffer_ctx, base); + + pvr_dump_buffer_advance(parent, ctx->nr_words * HEX_WORD_SIZE); + + return parent; +} + +static inline void pvr_dump_hex_print_prefix(const struct pvr_dump_hex_ctx *ctx, + const uint64_t offset) +{ + pvr_dump_printf(&ctx->base, + PVR_DUMP_OFFSET_PREFIX, + ctx->offset_digits, + offset * HEX_WORD_SIZE); +} + +#define pvr_dump_hex_println(ctx, offset, format, args...) \ + pvr_dump_println(&(ctx)->base, \ + PVR_DUMP_OFFSET_PREFIX format, \ + (ctx)->offset_digits, \ + offset, \ + ##args); + +#define pvr_dump_hex_println_no_prefix(ctx, format, args...) \ + pvr_dump_println(&(ctx)->base, \ + "%*c" format, \ + (ctx)->offset_digits + 3, \ + ' ', \ + ##args); + +static void +pvr_dump_hex_print_zero_lines(const struct pvr_dump_hex_ctx *const ctx, + const uint64_t zero_lines) +{ + const uint64_t zero_words = zero_lines * HEX_LINE_SIZE; + const uint64_t zero_bytes = zero_words * HEX_WORD_SIZE; + + if (zero_lines == 0) + return; + + pvr_dump_hex_println_no_prefix(ctx, + " + %" PRIu64 " zero line%s (%" PRIu64 + " words; %" PRIu64 "/0x%" PRIx64 " bytes)", + zero_lines, + zero_lines == 1 ? "" : "s", + zero_words, + zero_bytes, + zero_bytes); +} + +static void +pvr_dump_hex_print_trailing_zeroes(const struct pvr_dump_hex_ctx *const ctx) +{ + const uint64_t zero_words = + ctx->zero_lines * HEX_LINE_SIZE + ctx->prev_non_zero_trailing_zero_words; + const uint64_t zero_bytes = zero_words * HEX_WORD_SIZE; + + if (!ctx->prev_non_zero_trailing_zero_words) + return pvr_dump_hex_print_zero_lines(ctx, ctx->zero_lines); + + if (!ctx->zero_lines) + return; + + pvr_dump_hex_println_no_prefix(ctx, + " + %" PRIu64 "+%" PRIu32 + " zero lines (%" PRIu64 " words; %" PRIu64 + "/0x%" PRIx64 " bytes)", + ctx->zero_lines, + ctx->prev_non_zero_trailing_zero_words, + zero_words, + zero_bytes, + zero_bytes); +} + +static void pvr_dump_hex_print_line(struct pvr_dump_hex_ctx *ctx, + const uint32_t *const line_ptr, + const uint32_t truncate) +{ + const uint32_t nr_words = + MIN2(HEX_LINE_SIZE - truncate, ctx->end_ptr - line_ptr); + + pvr_dump_hex_print_prefix(ctx, line_ptr - ctx->start_ptr); + + for (uint32_t i = 0; i < nr_words; i++) { + if (i == HEX_LINE_SIZE / 2) + pvr_dump_printf_cont(&ctx->base, " "); + + pvr_dump_printf_cont(&ctx->base, " " HEX_WORD_FMT, line_ptr[i]); + } + + pvr_dump_print_eol(&ctx->base); +} + +static void pvr_dump_hex_process_line(struct pvr_dump_hex_ctx *const ctx) +{ + uint32_t trailing_zero_words = HEX_LINE_SIZE; + + for (uint32_t i = HEX_LINE_SIZE; i > 0; i--) { + if (ctx->line_ptr[i - 1]) { + trailing_zero_words = HEX_LINE_SIZE - i; + break; + } + } + + if (trailing_zero_words == HEX_LINE_SIZE) { + /* No non-zero words were found in this line; mark it and move on. */ + ctx->zero_lines++; + return; + } + + /* We have at least one non-zero word in this line. If we have a previous + * non-zero line stored, collapse and print any leading zero-only lines + * before it then print the stored line. + */ + if (ctx->prev_non_zero_line) { + pvr_dump_hex_print_zero_lines(ctx, ctx->prev_non_zero_leading_zero_lines); + pvr_dump_hex_print_line(ctx, ctx->prev_non_zero_line, 0); + } + + /* Now we store the current non-zero line for printing later. This way we + * can treat the last non-zero line specially. + */ + ctx->prev_non_zero_line = ctx->line_ptr; + ctx->prev_non_zero_leading_zero_lines = ctx->zero_lines; + ctx->prev_non_zero_trailing_zero_words = trailing_zero_words; + ctx->zero_lines = 0; +} + +static void pvr_dump_hex(struct pvr_dump_hex_ctx *const ctx) +{ + while (ctx->end_ptr - ctx->line_ptr > 0) { + pvr_dump_hex_process_line(ctx); + ctx->line_ptr += HEX_LINE_SIZE; + } + + if (ctx->prev_non_zero_line) { + pvr_dump_hex_print_zero_lines(ctx, ctx->prev_non_zero_leading_zero_lines); + pvr_dump_hex_print_line(ctx, + ctx->prev_non_zero_line, + ctx->prev_non_zero_trailing_zero_words); + + /* Collapse and print any trailing zeroes. */ + pvr_dump_hex_print_trailing_zeroes(ctx); + } else { + /* We made it to the end of the buffer without ever encountering a + * non-zero word. Make this known. + */ + pvr_dump_hex_println(ctx, UINT64_C(0), " "); + } + + pvr_dump_hex_println(ctx, ctx->nr_words, " "); +} + +bool pvr_dump_buffer_hex(struct pvr_dump_buffer_ctx *const ctx, + const uint64_t nr_words) +{ + struct pvr_dump_hex_ctx hex_ctx; + + if (!pvr_dump_hex_ctx_push(&hex_ctx, ctx, nr_words)) + return false; + + pvr_dump_hex(&hex_ctx); + + return !!pvr_dump_hex_ctx_pop(&hex_ctx); +} diff --git a/src/imagination/common/pvr_dump.h b/src/imagination/common/pvr_dump.h new file mode 100644 index 00000000000..d73c8a62625 --- /dev/null +++ b/src/imagination/common/pvr_dump.h @@ -0,0 +1,594 @@ +/* + * Copyright © 2022 Imagination Technologies 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. + */ + +#ifndef PVR_DUMP_H +#define PVR_DUMP_H + +#include +#include +#include +#include +#include + +#include "pvr_types.h" +#include "pvr_util.h" +#include "util/macros.h" +#include "util/u_math.h" + +/** BASIC PRINTING **/ + +#define PVR_DUMP_OFFSET_PREFIX "[%0*" PRIx64 "] " + +/** CONTEXTS **/ + +#define PVR_DUMP_INDENT_SIZE 2U +#define PVR_DUMP_FIELD_COLUMN_WIDTH 36U + +/* This is an invalid context used to permanently mark popped contexts as + * unusable. All operations on a context check that it's the "top" context + * by ensuring it has no active child. The only way to remove the active child + * of a context is by popping the active child directly. Assigning an invalid + * context as the active child of a context therefore makes it impossible to + * use. + */ +extern const struct pvr_dump_ctx __pvr_dump_ctx_invalid; + +struct pvr_dump_ctx { + /* This is const because only the "top" context should ever be modified. It's + * fine to extract information from the parent context, but not to modify it. + * There is *one* exception: pvr_dump_ctx_pop() must cast away the const to + * return the parent context as the new "top" context. This is considered + * sound because the parent context was not const when assigned here in + * pvr_dump_ctx_push(). + */ + const struct pvr_dump_ctx *parent; + + /* This is const because it's not meant to be used for access - it's just a + * way of checking if this context is the "top" context (see the comment on + * __pvr_dump_ctx_invalid for more details). Unlike parent, the const + * qualifier here should never be cast away. + */ + const struct pvr_dump_ctx *active_child; + + FILE *file; + const char *name; + + uint32_t allowed_child_depth; + uint32_t parent_indent; + + /* User-modifiable values */ + uint32_t indent; + bool ok; +}; + +static inline uint32_t +__pvr_dump_ctx_get_indent(const struct pvr_dump_ctx *const ctx) +{ + return (ctx->parent_indent + ctx->indent) * PVR_DUMP_INDENT_SIZE; +} + +struct pvr_dump_buffer_ctx { + struct pvr_dump_ctx base; + + const void *initial_ptr; + uint64_t capacity; + + /* User-modifiable values */ + const void *ptr; + uint64_t remaining_size; +}; + +#define pvr_dump_printf(ctx, format, args...) \ + pvr_dump_printf_cont(ctx, \ + "%*s" format, \ + __pvr_dump_ctx_get_indent(ctx), \ + "", \ + ##args) + +/* Same as pvr_dump_printf(), but with no indent. + * Intended for continuation lines. + */ +#define pvr_dump_printf_cont(ctx, format, args...) \ + fprintf((ctx)->file, format, ##args) + +#define pvr_dump_println(ctx, format, args...) \ + pvr_dump_printf(ctx, format "\n", ##args) + +#define pvr_dump_println_cont(ctx, format, args...) \ + pvr_dump_printf_cont(ctx, format "\n", ##args) + +#define pvr_dump_print_eol(ctx) fprintf((ctx)->file, "\n") + +#define pvr_dump_mark_section(ctx, format, args...) \ + do { \ + pvr_dump_print_eol(ctx); \ + pvr_dump_println(ctx, "------- " format " -------", ##args); \ + } while (0) + +#define pvr_dump_buffer_print_header_prefix(ctx) \ + do { \ + struct pvr_dump_buffer_ctx *_prefix_ctx = (ctx); \ + pvr_dump_printf(&_prefix_ctx->base, \ + PVR_DUMP_OFFSET_PREFIX, \ + u64_dec_digits(_prefix_ctx->capacity), \ + _prefix_ctx->capacity - _prefix_ctx->remaining_size); \ + } while (0) + +#define pvr_dump_buffer_print_header_line(ctx, format, args...) \ + do { \ + struct pvr_dump_buffer_ctx *_ctx = (ctx); \ + pvr_dump_buffer_print_header_prefix(_ctx); \ + pvr_dump_printf_cont(&_ctx->base, format "\n", ##args); \ + } while (0) + +#define pvr_dump_error(ctx, format, args...) \ + ({ \ + struct pvr_dump_ctx *_ctx = (ctx); \ + pvr_dump_println(_ctx, "", ##args); \ + _ctx->ok = false; \ + false; \ + }) + +static inline bool pvr_dump_ctx_require_top(struct pvr_dump_ctx *const ctx) +{ + if (ctx->active_child != NULL) + return pvr_dump_error(ctx, "use of non-top context"); + + return true; +} + +static inline void pvr_dump_indent(struct pvr_dump_ctx *const ctx) +{ + ctx->indent++; +} + +static inline void pvr_dump_dedent(struct pvr_dump_ctx *const ctx) +{ + if (ctx->indent) + ctx->indent--; +} + +static inline void __pvr_dump_ctx_init(struct pvr_dump_ctx *const ctx, + const struct pvr_dump_ctx *const parent, + FILE *const file, + const char *const name, + const uint32_t allowed_child_depth, + const uint32_t parent_indent) +{ + ctx->parent = parent; + ctx->active_child = NULL; + + ctx->file = file; + ctx->name = name; + + ctx->allowed_child_depth = allowed_child_depth; + ctx->parent_indent = parent_indent; + ctx->indent = 0; + ctx->ok = true; +} + +static inline void __pvr_dump_ctx_mark_popped(struct pvr_dump_ctx *const ctx) +{ + ctx->active_child = &__pvr_dump_ctx_invalid; +} + +static inline void pvr_dump_begin(struct pvr_dump_ctx *const root_ctx, + FILE *const file, + const char *const name, + const uint32_t max_depth) +{ + __pvr_dump_ctx_init(root_ctx, NULL, file, name, max_depth, 0); + + flockfile(file); + pvr_dump_println(root_ctx, "======= BEGIN %s =======", name); +} + +static inline bool pvr_dump_end(struct pvr_dump_ctx *const root_ctx) +{ + /* In order to end a dump, we must be in a root context (no parent) and have + * no active child context. + */ + if (!pvr_dump_ctx_require_top(root_ctx)) + return false; + + if (root_ctx->parent) + return pvr_dump_error(root_ctx, "ending non-root context"); + + pvr_dump_println(root_ctx, "======= END %s =======", root_ctx->name); + funlockfile(root_ctx->file); + + __pvr_dump_ctx_mark_popped(root_ctx); + + return true; +} + +static inline bool pvr_dump_ctx_push(struct pvr_dump_ctx *const ctx, + struct pvr_dump_ctx *const parent_ctx) +{ + if (!parent_ctx->ok) + return false; + + if (!parent_ctx->allowed_child_depth) + return pvr_dump_error(parent_ctx, "context stack depth limit reached"); + + __pvr_dump_ctx_init(ctx, + parent_ctx, + parent_ctx->file, + parent_ctx->name, + parent_ctx->allowed_child_depth - 1, + parent_ctx->parent_indent + parent_ctx->indent); + + parent_ctx->active_child = ctx; + + return true; +} + +static inline struct pvr_dump_ctx * +pvr_dump_ctx_pop(struct pvr_dump_ctx *const ctx) +{ + struct pvr_dump_ctx *const parent = (struct pvr_dump_ctx *)ctx->parent; + + if (!pvr_dump_ctx_require_top(ctx)) + return NULL; + + if (!parent) { + pvr_dump_error(ctx, "popped root context"); + return NULL; + } + + parent->active_child = NULL; + + __pvr_dump_ctx_mark_popped(ctx); + + return parent; +} + +static inline bool +pvr_dump_buffer_ctx_push(struct pvr_dump_buffer_ctx *const ctx, + struct pvr_dump_ctx *const parent_ctx, + const void *const initial_ptr, + const uint64_t size) +{ + if (!pvr_dump_ctx_push(&ctx->base, parent_ctx)) + return false; + + ctx->initial_ptr = initial_ptr; + ctx->capacity = size; + + ctx->ptr = initial_ptr; + ctx->remaining_size = size; + + return true; +} + +static inline struct pvr_dump_ctx * +pvr_dump_buffer_ctx_pop(struct pvr_dump_buffer_ctx *const ctx) +{ + return pvr_dump_ctx_pop(&ctx->base); +} + +bool pvr_dump_buffer_hex(struct pvr_dump_buffer_ctx *ctx, uint64_t nr_words); + +static inline void __pvr_dump_buffer_advance(struct pvr_dump_buffer_ctx *ctx, + const uint64_t nr_bytes) +{ + ctx->ptr += nr_bytes; + ctx->remaining_size -= nr_bytes; +} + +static inline bool pvr_dump_buffer_advance(struct pvr_dump_buffer_ctx *ctx, + const uint64_t nr_bytes) +{ + if (!ctx->base.ok || !pvr_dump_ctx_require_top(&ctx->base)) + return false; + + if (nr_bytes > ctx->remaining_size) + return pvr_dump_error(&ctx->base, "advanced past end of context buffer"); + + __pvr_dump_buffer_advance(ctx, nr_bytes); + + return true; +} + +static inline bool pvr_dump_buffer_truncate(struct pvr_dump_buffer_ctx *ctx, + const uint64_t remaining_size) +{ + if (!ctx->base.ok || !pvr_dump_ctx_require_top(&ctx->base)) + return false; + + if (remaining_size > ctx->remaining_size) + return pvr_dump_error(&ctx->base, "truncated to larger size"); + + ctx->remaining_size = remaining_size; + + return true; +} + +static inline const void *restrict +pvr_dump_buffer_peek(struct pvr_dump_buffer_ctx *const restrict ctx, + const uint64_t nr_bytes) +{ + if (!ctx->base.ok || !pvr_dump_ctx_require_top(&ctx->base)) + return NULL; + + if (nr_bytes > ctx->remaining_size) { + pvr_dump_error(&ctx->base, "peeked past end of context buffer"); + return NULL; + } + + return ctx->ptr; +} + +static inline const void *restrict +pvr_dump_buffer_take(struct pvr_dump_buffer_ctx *const restrict ctx, + const uint64_t nr_bytes) +{ + const void *const ptr = pvr_dump_buffer_peek(ctx, nr_bytes); + + if (ptr) + __pvr_dump_buffer_advance(ctx, nr_bytes); + + return ptr; +} + +static inline void +pvr_dump_buffer_restart(struct pvr_dump_buffer_ctx *const ctx) +{ + ctx->ptr = ctx->initial_ptr; + ctx->remaining_size = ctx->capacity; +} + +/***************************************************************************** + Field printers +*****************************************************************************/ + +#define pvr_dump_field(ctx, name, format, args...) \ + pvr_dump_println(ctx, \ + "%-*s : " format, \ + PVR_DUMP_FIELD_COLUMN_WIDTH - \ + __pvr_dump_ctx_get_indent(ctx), \ + name, \ + ##args) + +#define pvr_dump_field_computed(ctx, name, format, raw_format, args...) \ + pvr_dump_field(ctx, name, format " (" raw_format ")", ##args) + +#define pvr_dump_field_error(ctx, format, args...) \ + ({ \ + struct pvr_dump_ctx *_ctx = (ctx); \ + pvr_dump_field(_ctx, "", "<" format ">", ##args); \ + _ctx->ok = false; \ + false; \ + }) + +/***************************************************************************** + Field printers: integers +*****************************************************************************/ + +static inline void pvr_dump_field_u32(struct pvr_dump_ctx *const ctx, + const char *const name, + const uint32_t value) +{ + pvr_dump_field(ctx, name, "%" PRIu32, value); +} + +static inline void pvr_dump_field_u32_units(struct pvr_dump_ctx *const ctx, + const char *const name, + const uint32_t value, + const char *const units) +{ + pvr_dump_field(ctx, name, "%" PRIu32 " %s", value, units); +} + +static inline void pvr_dump_field_u32_offset(struct pvr_dump_ctx *const ctx, + const char *const name, + const uint32_t value, + const uint32_t offset) +{ + pvr_dump_field_computed(ctx, + name, + "%" PRIu32, + "%" PRIu32 " + %" PRIu32, + value + offset, + value, + offset); +} + +static inline void pvr_dump_field_u32_scaled(struct pvr_dump_ctx *const ctx, + const char *const name, + const uint32_t value, + const uint32_t scale) +{ + pvr_dump_field_computed(ctx, + name, + "%" PRIu32, + "%" PRIu32 " x %" PRIu32, + value * scale, + value, + scale); +} + +static inline void +pvr_dump_field_u32_scaled_units(struct pvr_dump_ctx *const ctx, + const char *const name, + const uint32_t value, + const uint32_t scale, + const char *const units) +{ + pvr_dump_field_computed(ctx, + name, + "%" PRIu32 " %s", + "%" PRIu32 " x %" PRIu32 " %s", + value * scale, + units, + value, + scale, + units); +} + +static inline void pvr_dump_field_u32_zero(struct pvr_dump_ctx *const ctx, + const char *const name, + const uint32_t value, + const uint32_t zero_value) +{ + if (value) + pvr_dump_field_u32(ctx, name, value); + else + pvr_dump_field_computed(ctx, name, "%" PRIu32, "0", zero_value); +} + +static inline void pvr_dump_field_x32(struct pvr_dump_ctx *const ctx, + const char *const name, + const uint32_t value, + const uint32_t chars) +{ + pvr_dump_field(ctx, + name, + "0x%0*" PRIx32, + chars, + value & BITFIELD_MASK(chars * 4)); +} + +/***************************************************************************** + Field printers: device address +*****************************************************************************/ + +static inline void pvr_dump_field_addr_non_null(struct pvr_dump_ctx *const ctx, + const char *const name, + const pvr_dev_addr_t value) +{ + pvr_dump_field(ctx, name, PVR_DEV_ADDR_FMT, value.addr); +} + +static inline void pvr_dump_field_addr(struct pvr_dump_ctx *const ctx, + const char *const name, + const pvr_dev_addr_t value) +{ + if (value.addr) + pvr_dump_field_addr_non_null(ctx, name, value); + else + pvr_dump_field(ctx, name, ""); +} + +static inline void pvr_dump_field_addr_split(struct pvr_dump_ctx *const ctx, + const char *const name, + const pvr_dev_addr_t msb, + const pvr_dev_addr_t lsb) +{ + pvr_dump_field_addr(ctx, name, PVR_DEV_ADDR(msb.addr | lsb.addr)); + + pvr_dump_indent(ctx); + pvr_dump_field_addr_non_null(ctx, "msb", msb); + pvr_dump_field_addr_non_null(ctx, "lsb", lsb); + pvr_dump_dedent(ctx); +} + +/***************************************************************************** + Field printers: enums +*****************************************************************************/ + +#define pvr_dump_field_enum(ctx, name, value, to_str) \ + do { \ + __typeof__(value) _value = (value); \ + const char *_str = to_str(_value); \ + if (!_str) \ + _str = ""; \ + pvr_dump_field_computed(ctx, name, "%s", "%u", _str, _value); \ + } while (0) + +static inline const char *__bool_to_str(const bool b) +{ + return b ? "yes" : "no"; +} + +/* A bool is just an enum with two values. */ +static inline void pvr_dump_field_bool(struct pvr_dump_ctx *const ctx, + const char *const name, + const bool value) +{ + pvr_dump_field_enum(ctx, name, value, __bool_to_str); +} + +/***************************************************************************** + Field printers: not present +*****************************************************************************/ + +static inline void pvr_dump_field_no_fields(struct pvr_dump_ctx *const ctx) +{ + pvr_dump_println(ctx, ""); +} + +static inline void pvr_dump_field_not_present(struct pvr_dump_ctx *const ctx, + const char *const name) +{ + pvr_dump_field(ctx, name, ""); +} + +/***************************************************************************** + Field printers: helpers for members +*****************************************************************************/ + +/* clang-format off */ + +#define pvr_dump_field_member_u32(ctx, compound, member) \ + pvr_dump_field_u32(ctx, #member, (compound)->member) + +#define pvr_dump_field_member_u32_units(ctx, compound, member, units) \ + pvr_dump_field_u32_units(ctx, #member, (compound)->member, units) + +#define pvr_dump_field_member_u32_offset(ctx, compound, member, offset) \ + pvr_dump_field_u32_offset(ctx, #member, (compound)->member, offset) + +#define pvr_dump_field_member_u32_scaled(ctx, compound, member, scale) \ + pvr_dump_field_u32_scaled(ctx, #member, (compound)->member, scale) + +#define pvr_dump_field_member_u32_scaled_units(ctx, compound, member, scale, units) \ + pvr_dump_field_u32_scaled_units(ctx, #member, (compound)->member, scale, units) + +#define pvr_dump_field_member_u32_zero(ctx, compound, member, zero_value) \ + pvr_dump_field_u32_zero(ctx, #member, (compound)->member, zero_value) + +#define pvr_dump_field_member_x32(ctx, compound, member, chars) \ + pvr_dump_field_x32(ctx, #member, (compound)->member, chars) + +#define pvr_dump_field_member_f32(ctx, compound, member) \ + pvr_dump_field_f32(ctx, #member, (compound)->member) + +#define pvr_dump_field_member_addr(ctx, compound, member) \ + pvr_dump_field_addr(ctx, #member, (compound)->member) + +#define pvr_dump_field_member_enum(ctx, compound, member, to_str) \ + pvr_dump_field_enum(ctx, #member, (compound)->member, to_str) + +#define pvr_dump_field_member_bool(ctx, compound, member) \ + pvr_dump_field_bool(ctx, #member, (compound)->member) + +/* clang-format on */ + +#define pvr_dump_field_member_not_present(ctx, compound, member) \ + do { \ + (void)&(compound)->member; \ + pvr_dump_field_not_present(ctx, #member); \ + } while (0) + +#endif /* PVR_DUMP_H */ diff --git a/src/imagination/common/pvr_util.h b/src/imagination/common/pvr_util.h index a7f21a12794..e534bc04985 100644 --- a/src/imagination/common/pvr_util.h +++ b/src/imagination/common/pvr_util.h @@ -24,9 +24,18 @@ #ifndef PVR_UTIL_H #define PVR_UTIL_H +#include #include #include "util/bitscan.h" +#include "util/macros.h" + +static inline bool ptr_is_aligned(const void *const ptr, + const uint32_t alignment) +{ + assert(util_is_power_of_two_nonzero(alignment)); + return ((uintptr_t)(ptr) & (alignment - 1)) == 0; +} /***************************************************************************** Math functions diff --git a/src/imagination/include/pvr_types.h b/src/imagination/include/pvr_types.h index ccdfc2432bd..eaf8ca2c63c 100644 --- a/src/imagination/include/pvr_types.h +++ b/src/imagination/include/pvr_types.h @@ -24,6 +24,7 @@ #ifndef PVR_TYPES_H #define PVR_TYPES_H +#include #include /***************************************************************************** @@ -38,4 +39,7 @@ typedef struct pvr_dev_addr { #define PVR_DEV_ADDR_OFFSET(base, offset) PVR_DEV_ADDR((base).addr + (offset)) #define PVR_DEV_ADDR_INVALID PVR_DEV_ADDR(0) +/* All currently supported devices use a 40-bit virtual address space. */ +#define PVR_DEV_ADDR_FMT "0x%010" PRIx64 + #endif /* PVR_TYPES_H */ diff --git a/src/imagination/vulkan/meson.build b/src/imagination/vulkan/meson.build index 577f8e06ea4..7c3064b6399 100644 --- a/src/imagination/vulkan/meson.build +++ b/src/imagination/vulkan/meson.build @@ -42,6 +42,8 @@ pvr_files = files( 'pvr_csb.c', 'pvr_descriptor_set.c', 'pvr_device.c', + 'pvr_dump_bo.c', + 'pvr_dump_csb.c', 'pvr_formats.c', 'pvr_hardcode.c', 'pvr_hw_pass.c', diff --git a/src/imagination/vulkan/pvr_bo.c b/src/imagination/vulkan/pvr_bo.c index 04350b280ab..710d17474a9 100644 --- a/src/imagination/vulkan/pvr_bo.c +++ b/src/imagination/vulkan/pvr_bo.c @@ -27,12 +27,62 @@ #include #include "pvr_bo.h" +#include "pvr_dump.h" #include "pvr_private.h" #include "pvr_types.h" #include "pvr_winsys.h" #include "vk_alloc.h" #include "vk_log.h" +static void pvr_bo_dump_line(struct pvr_dump_ctx *const ctx, + const struct pvr_bo *bo, + const uint32_t index, + const uint32_t nr_bos_log10) +{ + static const char *const pretty_sizes[64 + 1] = { + "", "1 B", "2 B", "4 B", "8 B", "16 B", + "32 B", "64 B", "128 B", "256 B", "512 B", "1 KiB", + "2 KiB", "4 KiB", "8 KiB", "16 KiB", "32 KiB", "64 KiB", + "128 KiB", "256 KiB", "512 KiB", "1 MiB", "2 MiB", "4 MiB", + "8 MiB", "16 MiB", "32 MiB", "64 MiB", "128 MiB", "256 MiB", + "512 MiB", "1 GiB", "2 GiB", "4 GiB", "8 GiB", "16 GiB", + "32 GiB", "64 GiB", "128 GiB", "256 GiB", "512 GiB", "1 TiB", + "2 TiB", "4 TiB", "8 TiB", "16 TiB", "32 TiB", "64 TiB", + "128 TiB", "256 TiB", "512 TiB", "1 PiB", "2 PiB", "4 PiB", + "8 PiB", "16 PiB", "32 PiB", "64 PiB", "128 PiB", "256 PiB", + "512 PiB", "1 EiB", "2 EiB", "4 EiB", "8 EiB", + }; + + const uint64_t size = bo->vma->size; + const uint32_t size_log2 = + util_is_power_of_two_or_zero64(size) ? util_last_bit(size) : 0; + + pvr_dump_println(ctx, + "[%0*" PRIu32 "] " PVR_DEV_ADDR_FMT " -> %*p " + "(%s%s0x%" PRIx64 " bytes)", + nr_bos_log10, + index, + bo->vma->dev_addr.addr, + (int)sizeof(void *) * 2 + 2, /* nr hex digits + 0x prefix */ + bo->bo->map, + pretty_sizes[size_log2], + size_log2 ? ", " : "", + size); +} + +void pvr_bo_list_dump(struct pvr_dump_ctx *const ctx, + const struct list_head *const bo_list, + const uint32_t nr_bos) +{ + const uint32_t real_nr_bos = nr_bos ? nr_bos : list_length(bo_list); + const uint32_t nr_bos_log10 = u32_dec_digits(real_nr_bos); + uint32_t bo_idx = 0; + + list_for_each_entry (struct pvr_bo, bo, bo_list, link) { + pvr_bo_dump_line(ctx, bo, bo_idx++, nr_bos_log10); + } +} + static uint32_t pvr_bo_alloc_to_winsys_flags(uint64_t flags) { uint32_t ws_flags = 0; @@ -103,6 +153,9 @@ VkResult pvr_bo_alloc(struct pvr_device *device, result = VK_ERROR_MEMORY_MAP_FAILED; goto err_buffer_destroy; } + + if (flags & PVR_BO_ALLOC_FLAG_ZERO_ON_ALLOC) + VG(VALGRIND_MAKE_MEM_DEFINED(map, pvr_bo->bo->size)); } pvr_bo->vma = device->ws->ops->heap_alloc(heap, size, alignment); diff --git a/src/imagination/vulkan/pvr_bo.h b/src/imagination/vulkan/pvr_bo.h index bc7fe32b5e4..7f870746e1b 100644 --- a/src/imagination/vulkan/pvr_bo.h +++ b/src/imagination/vulkan/pvr_bo.h @@ -32,6 +32,7 @@ #include "util/macros.h" struct pvr_device; +struct pvr_dump_ctx; struct pvr_winsys_bo; struct pvr_winsys_vma; struct pvr_winsys_heap; @@ -98,4 +99,8 @@ static ALWAYS_INLINE void *pvr_bo_cpu_map_unchanged(struct pvr_device *device, } #endif /* defined(HAVE_VALGRIND) */ +void pvr_bo_list_dump(struct pvr_dump_ctx *ctx, + const struct list_head *bo_list, + uint32_t bo_size); + #endif /* PVR_BO_H */ diff --git a/src/imagination/vulkan/pvr_csb.c b/src/imagination/vulkan/pvr_csb.c index 66da6fbbd42..2ad014e7add 100644 --- a/src/imagination/vulkan/pvr_csb.c +++ b/src/imagination/vulkan/pvr_csb.c @@ -36,6 +36,7 @@ #include "hwdef/rogue_hw_utils.h" #include "pvr_bo.h" #include "pvr_csb.h" +#include "pvr_debug.h" #include "pvr_device_info.h" #include "pvr_private.h" #include "util/list.h" @@ -61,11 +62,6 @@ * type control streams. */ -/** - * \brief Size of the individual csb buffer object. - */ -#define PVR_CMD_BUFFER_CSB_BO_SIZE 4096 - /** * \brief Initializes the csb object. * @@ -137,9 +133,17 @@ static bool pvr_csb_buffer_extend(struct pvr_csb *csb) sizeof(uint32_t); const uint32_t cache_line_size = rogue_get_slc_cache_line_size(&csb->device->pdevice->dev_info); + uint64_t alloc_flags = PVR_BO_ALLOC_FLAG_CPU_MAPPED; struct pvr_bo *pvr_bo; VkResult result; + /* If we're dumping the control stream, ensure the buffer is zeroed to make + * the contents deterministic. This keeps valgrind happy and makes for + * cleaner dump output. + */ + if (PVR_IS_DEBUG_SET(DUMP_CONTROL_STREAM)) + alloc_flags |= PVR_BO_ALLOC_FLAG_ZERO_ON_ALLOC; + /* Make sure extra space allocated for stream links is sufficient for both * stream types. */ @@ -152,7 +156,7 @@ static bool pvr_csb_buffer_extend(struct pvr_csb *csb) csb->device->heaps.general_heap, PVR_CMD_BUFFER_CSB_BO_SIZE, cache_line_size, - PVR_BO_ALLOC_FLAG_CPU_MAPPED, + alloc_flags, &pvr_bo); if (result != VK_SUCCESS) { vk_error(csb->device, result); diff --git a/src/imagination/vulkan/pvr_csb.h b/src/imagination/vulkan/pvr_csb.h index fa21988725b..91f30887dff 100644 --- a/src/imagination/vulkan/pvr_csb.h +++ b/src/imagination/vulkan/pvr_csb.h @@ -50,6 +50,11 @@ #include "csbgen/rogue_hwdefs.h" +/** + * \brief Size of the individual csb buffer object. + */ +#define PVR_CMD_BUFFER_CSB_BO_SIZE 4096 + struct pvr_device; enum pvr_cmd_stream_type { @@ -132,6 +137,10 @@ void pvr_csb_emit_link(struct pvr_csb *csb, pvr_dev_addr_t addr, bool ret); VkResult pvr_csb_emit_return(struct pvr_csb *csb); VkResult pvr_csb_emit_terminate(struct pvr_csb *csb); +void pvr_csb_dump(const struct pvr_csb *csb, + uint32_t frame_num, + uint32_t job_num); + #define PVRX(x) ROGUE_##x #define pvr_cmd_length(x) PVRX(x##_length) #define pvr_cmd_header(x) PVRX(x##_header) diff --git a/src/imagination/vulkan/pvr_csb_enum_helpers.h b/src/imagination/vulkan/pvr_csb_enum_helpers.h index 725a90107d5..e5dea4f2fd2 100644 --- a/src/imagination/vulkan/pvr_csb_enum_helpers.h +++ b/src/imagination/vulkan/pvr_csb_enum_helpers.h @@ -31,6 +31,21 @@ #include "rogue/rogue.h" #include "util/macros.h" +static const char * +pvr_cmd_stream_type_to_str(const enum pvr_cmd_stream_type stream_type) +{ + switch (stream_type) { + case PVR_CMD_STREAM_TYPE_INVALID: + return "INVALID"; + case PVR_CMD_STREAM_TYPE_GRAPHICS: + return "GRAPHICS"; + case PVR_CMD_STREAM_TYPE_COMPUTE: + return "COMPUTE"; + default: + return NULL; + } +} + /****************************************************************************** CR ******************************************************************************/ diff --git a/src/imagination/vulkan/pvr_dump_bo.c b/src/imagination/vulkan/pvr_dump_bo.c new file mode 100644 index 00000000000..89d1225d51d --- /dev/null +++ b/src/imagination/vulkan/pvr_dump_bo.c @@ -0,0 +1,82 @@ +/* + * Copyright © 2022 Imagination Technologies 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 "pvr_bo.h" +#include "pvr_dump_bo.h" +#include "pvr_dump.h" +#include "pvr_winsys.h" +#include "util/u_math.h" + +struct pvr_device; + +bool pvr_dump_bo_ctx_push(struct pvr_dump_bo_ctx *const ctx, + struct pvr_dump_ctx *const parent_ctx, + struct pvr_device *const device, + struct pvr_bo *const bo) +{ + bool did_map_bo = false; + + if (!bo->bo->map) { + if (!pvr_bo_cpu_map_unchanged(device, bo)) + goto err_out; + + did_map_bo = true; + } + + if (bo->bo->size > UINT32_MAX) { + mesa_logw_once("Attempted to dump a BO larger than 4GiB; time to rework" + "pvr_dump_buffer_ctx to use 64-bit sizes."); + goto err_unmap_bo; + } + + if (!pvr_dump_buffer_ctx_push(&ctx->base, + parent_ctx, + bo->bo->map, + bo->bo->size)) { + goto err_unmap_bo; + } + + ctx->device = device; + ctx->bo = bo; + ctx->bo_mapped_in_ctx = did_map_bo; + + return true; + +err_unmap_bo: + if (did_map_bo) + pvr_bo_cpu_unmap(device, bo); + +err_out: + return false; +} + +bool pvr_dump_bo_ctx_pop(struct pvr_dump_bo_ctx *const ctx) +{ + if (ctx->bo_mapped_in_ctx) + pvr_bo_cpu_unmap(ctx->device, ctx->bo); + + return pvr_dump_buffer_ctx_pop(&ctx->base); +} diff --git a/src/imagination/vulkan/pvr_dump_bo.h b/src/imagination/vulkan/pvr_dump_bo.h new file mode 100644 index 00000000000..92edd7f2844 --- /dev/null +++ b/src/imagination/vulkan/pvr_dump_bo.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2022 Imagination Technologies 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. + */ + +#ifndef PVR_DUMP_BO_H +#define PVR_DUMP_BO_H + +#include + +#include "pvr_dump.h" + +struct pvr_bo; +struct pvr_device; + +struct pvr_dump_bo_ctx { + struct pvr_dump_buffer_ctx base; + + struct pvr_device *device; + struct pvr_bo *bo; + bool bo_mapped_in_ctx; + + /* No user-modifiable values */ +}; + +bool pvr_dump_bo_ctx_push(struct pvr_dump_bo_ctx *ctx, + struct pvr_dump_ctx *parent_ctx, + struct pvr_device *device, + struct pvr_bo *bo); +bool pvr_dump_bo_ctx_pop(struct pvr_dump_bo_ctx *ctx); + +#endif /* PVR_DUMP_BO_H */ diff --git a/src/imagination/vulkan/pvr_dump_csb.c b/src/imagination/vulkan/pvr_dump_csb.c new file mode 100644 index 00000000000..a1f9aa20614 --- /dev/null +++ b/src/imagination/vulkan/pvr_dump_csb.c @@ -0,0 +1,1108 @@ +/* + * Copyright © 2022 Imagination Technologies 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 "pvr_bo.h" +#include "pvr_csb.h" +#include "pvr_csb_enum_helpers.h" +#include "pvr_device_info.h" +#include "pvr_dump.h" +#include "pvr_dump_bo.h" +#include "pvr_private.h" +#include "pvr_util.h" +#include "util/list.h" +#include "util/macros.h" +#include "util/u_math.h" +#include "vulkan/util/vk_enum_to_str.h" + +/***************************************************************************** + Utilities + ******************************************************************************/ + +#define PVR_DUMP_CSB_WORD_SIZE ((unsigned)sizeof(uint32_t)) + +struct pvr_dump_csb_ctx { + struct pvr_dump_buffer_ctx base; + + /* User-modifiable values */ + uint32_t next_block_idx; +}; + +static inline bool +pvr_dump_csb_ctx_push(struct pvr_dump_csb_ctx *const ctx, + struct pvr_dump_buffer_ctx *const parent_ctx) +{ + if (!pvr_dump_buffer_ctx_push(&ctx->base, + &parent_ctx->base, + parent_ctx->ptr, + parent_ctx->remaining_size)) { + return false; + } + + ctx->next_block_idx = 0; + + return true; +} + +static inline struct pvr_dump_buffer_ctx * +pvr_dump_csb_ctx_pop(struct pvr_dump_csb_ctx *const ctx, bool advance_parent) +{ + struct pvr_dump_buffer_ctx *parent; + struct pvr_dump_ctx *parent_base; + const uint64_t unused_words = + ctx->base.remaining_size / PVR_DUMP_CSB_WORD_SIZE; + + if (unused_words) { + pvr_dump_buffer_print_header_line(&ctx->base, + "<%" PRIu64 " unused word%s (%" PRIu64 + " bytes)>", + unused_words, + unused_words == 1 ? "" : "s", + unused_words * PVR_DUMP_CSB_WORD_SIZE); + + pvr_dump_buffer_advance(&ctx->base, + unused_words * PVR_DUMP_CSB_WORD_SIZE); + } + + pvr_dump_buffer_print_header_line(&ctx->base, ""); + + parent_base = pvr_dump_buffer_ctx_pop(&ctx->base); + if (!parent_base) + return NULL; + + parent = container_of(parent_base, struct pvr_dump_buffer_ctx, base); + + if (advance_parent) + pvr_dump_buffer_advance(parent, ctx->base.capacity); + + return parent; +} + +struct pvr_dump_csb_block_ctx { + struct pvr_dump_buffer_ctx base; +}; + +#define pvr_dump_csb_block_ctx_push(ctx, \ + parent_ctx, \ + header_format, \ + header_args...) \ + ({ \ + struct pvr_dump_csb_ctx *const _csb_ctx = (parent_ctx); \ + pvr_dump_buffer_print_header_line(&_csb_ctx->base, \ + "%" PRIu32 ": " header_format, \ + _csb_ctx->next_block_idx, \ + ##header_args); \ + __pvr_dump_csb_block_ctx_push(ctx, _csb_ctx); \ + }) + +static inline bool +__pvr_dump_csb_block_ctx_push(struct pvr_dump_csb_block_ctx *const ctx, + struct pvr_dump_csb_ctx *const parent_ctx) +{ + pvr_dump_indent(&parent_ctx->base.base); + + if (!pvr_dump_buffer_ctx_push(&ctx->base, + &parent_ctx->base.base, + parent_ctx->base.ptr, + parent_ctx->base.remaining_size)) { + return false; + } + + parent_ctx->next_block_idx++; + + return true; +} + +static inline struct pvr_dump_csb_ctx * +pvr_dump_csb_block_ctx_pop(struct pvr_dump_csb_block_ctx *const ctx) +{ + const uint64_t used_size = ctx->base.capacity - ctx->base.remaining_size; + struct pvr_dump_csb_ctx *parent_ctx; + struct pvr_dump_ctx *parent_base; + + parent_base = pvr_dump_buffer_ctx_pop(&ctx->base); + if (!parent_base) + return NULL; + + parent_ctx = container_of(parent_base, struct pvr_dump_csb_ctx, base.base); + + /* No need to check this since it can never fail. */ + pvr_dump_buffer_advance(&parent_ctx->base, used_size); + + pvr_dump_dedent(parent_base); + + return parent_ctx; +} + +static inline const uint32_t * +pvr_dump_csb_block_take(struct pvr_dump_csb_block_ctx *const restrict ctx, + const uint32_t nr_words) +{ + return pvr_dump_buffer_take(&ctx->base, nr_words * PVR_DUMP_CSB_WORD_SIZE); +} + +#define pvr_dump_csb_block_take_packed(ctx, cmd, dest) \ + ({ \ + struct pvr_dump_csb_block_ctx *const _block_ctx = (ctx); \ + struct PVRX(cmd) *const _dest = (dest); \ + const void *const _ptr = \ + pvr_dump_csb_block_take(_block_ctx, pvr_cmd_length(cmd)); \ + if (_ptr) { \ + pvr_cmd_unpack(cmd)(_ptr, _dest); \ + } else { \ + pvr_dump_field_error(&_block_ctx->base.base, \ + "failed to unpack word(s)"); \ + } \ + !!_ptr; \ + }) + +/***************************************************************************** + Feature dumping + ******************************************************************************/ + +static inline void +__pvr_dump_field_needs_feature(struct pvr_dump_ctx *const ctx, + const char *const name, + const char *const feature) +{ + pvr_dump_field(ctx, name, "", feature); +} + +#define pvr_dump_field_needs_feature(ctx, name, feature) \ + do { \ + (void)PVR_HAS_FEATURE((struct pvr_device_info *)NULL, feature); \ + __pvr_dump_field_needs_feature(ctx, name, #feature); \ + } while (0) + +#define pvr_dump_field_member_needs_feature(ctx, compound, member, feature) \ + do { \ + (void)&(compound)->member; \ + pvr_dump_field_needs_feature(ctx, #member, feature); \ + } while (0) + +/****************************************************************************** + Block printers + ******************************************************************************/ + +static bool print_block_cdmctrl_kernel(struct pvr_dump_csb_ctx *const csb_ctx) +{ + struct pvr_dump_csb_block_ctx ctx; + struct pvr_dump_ctx *const base_ctx = &ctx.base.base; + bool ret = false; + + struct PVRX(CDMCTRL_KERNEL0) kernel0; + struct PVRX(CDMCTRL_KERNEL1) kernel1; + struct PVRX(CDMCTRL_KERNEL2) kernel2; + struct PVRX(CDMCTRL_KERNEL3) kernel3; + struct PVRX(CDMCTRL_KERNEL4) kernel4; + struct PVRX(CDMCTRL_KERNEL5) kernel5; + struct PVRX(CDMCTRL_KERNEL6) kernel6; + struct PVRX(CDMCTRL_KERNEL7) kernel7; + struct PVRX(CDMCTRL_KERNEL8) kernel8; + struct PVRX(CDMCTRL_KERNEL9) kernel9; + struct PVRX(CDMCTRL_KERNEL10) kernel10; + struct PVRX(CDMCTRL_KERNEL11) kernel11; + + if (!pvr_dump_csb_block_ctx_push(&ctx, csb_ctx, "KERNEL")) + goto end_out; + + if (!pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_KERNEL0, &kernel0) || + !pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_KERNEL1, &kernel1) || + !pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_KERNEL2, &kernel2)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_bool(base_ctx, &kernel0, indirect_present); + pvr_dump_field_member_bool(base_ctx, &kernel0, global_offsets_present); + pvr_dump_field_member_bool(base_ctx, &kernel0, event_object_present); + pvr_dump_field_member_u32_scaled_units( + base_ctx, + &kernel0, + usc_common_size, + PVRX(CDMCTRL_KERNEL0_USC_COMMON_SIZE_UNIT_SIZE), + "bytes"); + pvr_dump_field_member_u32_scaled_units( + base_ctx, + &kernel0, + usc_unified_size, + PVRX(CDMCTRL_KERNEL0_USC_UNIFIED_SIZE_UNIT_SIZE), + "bytes"); + pvr_dump_field_member_u32_scaled_units( + base_ctx, + &kernel0, + pds_temp_size, + PVRX(CDMCTRL_KERNEL0_PDS_TEMP_SIZE_UNIT_SIZE), + "bytes"); + pvr_dump_field_member_u32_scaled_units( + base_ctx, + &kernel0, + pds_data_size, + PVRX(CDMCTRL_KERNEL0_PDS_DATA_SIZE_UNIT_SIZE), + "bytes"); + pvr_dump_field_member_enum(base_ctx, + &kernel0, + usc_target, + pvr_cmd_enum_to_str(CDMCTRL_USC_TARGET)); + pvr_dump_field_member_bool(base_ctx, &kernel0, fence); + + pvr_dump_field_member_addr(base_ctx, &kernel1, data_addr); + pvr_dump_field_member_enum(base_ctx, + &kernel1, + sd_type, + pvr_cmd_enum_to_str(CDMCTRL_SD_TYPE)); + pvr_dump_field_member_bool(base_ctx, &kernel1, usc_common_shared); + + pvr_dump_field_member_addr(base_ctx, &kernel2, code_addr); + pvr_dump_field_member_bool(base_ctx, &kernel2, one_wg_per_task); + + if (!kernel0.indirect_present) { + if (!pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_KERNEL3, &kernel3) || + !pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_KERNEL4, &kernel4) || + !pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_KERNEL5, &kernel5)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_u32_offset(base_ctx, &kernel3, workgroup_x, 1); + pvr_dump_field_member_u32_offset(base_ctx, &kernel4, workgroup_y, 1); + pvr_dump_field_member_u32_offset(base_ctx, &kernel5, workgroup_z, 1); + + pvr_dump_field_not_present(base_ctx, "indirect_addr"); + } else { + if (!pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_KERNEL6, &kernel6) || + !pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_KERNEL7, &kernel7)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_not_present(base_ctx, &kernel3, workgroup_x); + pvr_dump_field_member_not_present(base_ctx, &kernel4, workgroup_y); + pvr_dump_field_member_not_present(base_ctx, &kernel5, workgroup_z); + + pvr_dump_field_addr_split(base_ctx, + "indirect_addr", + kernel6.indirect_addrmsb, + kernel7.indirect_addrlsb); + } + + if (!pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_KERNEL8, &kernel8)) + goto end_pop_ctx; + + pvr_dump_field_member_u32_zero(base_ctx, &kernel8, max_instances, 32); + pvr_dump_field_member_u32_offset(base_ctx, &kernel8, workgroup_size_x, 1); + pvr_dump_field_member_u32_offset(base_ctx, &kernel8, workgroup_size_y, 1); + pvr_dump_field_member_u32_offset(base_ctx, &kernel8, workgroup_size_z, 1); + + if (kernel0.event_object_present) { + if (!pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_KERNEL9, &kernel9) || + !pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_KERNEL10, &kernel10) || + !pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_KERNEL11, &kernel11)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_u32(base_ctx, &kernel9, global_offset_x); + pvr_dump_field_member_u32(base_ctx, &kernel10, global_offset_y); + pvr_dump_field_member_u32(base_ctx, &kernel11, global_offset_z); + } else { + pvr_dump_field_member_not_present(base_ctx, &kernel9, global_offset_x); + pvr_dump_field_member_not_present(base_ctx, &kernel10, global_offset_y); + pvr_dump_field_member_not_present(base_ctx, &kernel11, global_offset_z); + } + + ret = true; + +end_pop_ctx: + pvr_dump_csb_block_ctx_pop(&ctx); + +end_out: + return ret; +} + +static bool +print_block_cdmctrl_stream_link(struct pvr_dump_csb_ctx *const csb_ctx) +{ + struct pvr_dump_csb_block_ctx ctx; + struct pvr_dump_ctx *const base_ctx = &ctx.base.base; + bool ret = false; + + struct PVRX(CDMCTRL_STREAM_LINK0) link0; + struct PVRX(CDMCTRL_STREAM_LINK1) link1; + + if (!pvr_dump_csb_block_ctx_push(&ctx, csb_ctx, "STREAM_LINK")) + goto end_out; + + if (!pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_STREAM_LINK0, &link0) || + !pvr_dump_csb_block_take_packed(&ctx, CDMCTRL_STREAM_LINK1, &link1)) { + goto end_pop_ctx; + } + + pvr_dump_field_addr_split(base_ctx, + "link_addr", + link0.link_addrmsb, + link1.link_addrlsb); + + ret = true; + +end_pop_ctx: + pvr_dump_csb_block_ctx_pop(&ctx); + +end_out: + return ret; +} + +static bool +print_block_cdmctrl_stream_terminate(struct pvr_dump_csb_ctx *const csb_ctx) +{ + struct pvr_dump_csb_block_ctx ctx; + struct pvr_dump_ctx *const base_ctx = &ctx.base.base; + bool ret = false; + + struct PVRX(CDMCTRL_STREAM_TERMINATE) terminate; + + if (!pvr_dump_csb_block_ctx_push(&ctx, csb_ctx, "TERMINATE")) + goto end_out; + + if (!pvr_dump_csb_block_take_packed(&ctx, + CDMCTRL_STREAM_TERMINATE, + &terminate)) { + goto end_pop_ctx; + } + + pvr_dump_field_no_fields(base_ctx); + + ret = true; + +end_pop_ctx: + pvr_dump_csb_block_ctx_pop(&ctx); + +end_out: + return ret; +} + +static bool +print_block_vdmctrl_ppp_state_update(struct pvr_dump_csb_ctx *const csb_ctx, + struct pvr_device *const device) +{ + struct pvr_dump_csb_block_ctx ctx; + struct pvr_dump_ctx *const base_ctx = &ctx.base.base; + bool ret = false; + + struct PVRX(VDMCTRL_PPP_STATE0) state0; + struct PVRX(VDMCTRL_PPP_STATE1) state1; + + if (!pvr_dump_csb_block_ctx_push(&ctx, csb_ctx, "PPP_STATE_UPDATE")) + goto end_out; + + if (!pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_PPP_STATE0, &state0) || + !pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_PPP_STATE1, &state1)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_u32_zero(base_ctx, &state0, word_count, 256); + pvr_dump_field_addr_split(base_ctx, "addr", state0.addrmsb, state1.addrlsb); + + ret = true; + +end_pop_ctx: + pvr_dump_csb_block_ctx_pop(&ctx); + +end_out: + return ret; +} + +static bool +print_block_vdmctrl_pds_state_update(struct pvr_dump_csb_ctx *const csb_ctx) +{ + struct pvr_dump_csb_block_ctx ctx; + struct pvr_dump_ctx *const base_ctx = &ctx.base.base; + bool ret = false; + + struct PVRX(VDMCTRL_PDS_STATE0) state0; + struct PVRX(VDMCTRL_PDS_STATE1) state1; + struct PVRX(VDMCTRL_PDS_STATE2) state2; + + if (!pvr_dump_csb_block_ctx_push(&ctx, csb_ctx, "PDS_STATE_UPDATE")) + goto end_out; + + if (!pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_PDS_STATE0, &state0) || + !pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_PDS_STATE1, &state1) || + !pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_PDS_STATE2, &state2)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_enum(base_ctx, + &state0, + dm_target, + pvr_cmd_enum_to_str(VDMCTRL_DM_TARGET)); + pvr_dump_field_member_enum(base_ctx, + &state0, + usc_target, + pvr_cmd_enum_to_str(VDMCTRL_USC_TARGET)); + pvr_dump_field_member_u32_scaled_units( + base_ctx, + &state0, + usc_common_size, + PVRX(VDMCTRL_PDS_STATE0_USC_COMMON_SIZE_UNIT_SIZE), + "bytes"); + pvr_dump_field_member_u32_scaled_units( + base_ctx, + &state0, + usc_unified_size, + PVRX(VDMCTRL_PDS_STATE0_USC_UNIFIED_SIZE_UNIT_SIZE), + "bytes"); + pvr_dump_field_member_u32_scaled_units( + base_ctx, + &state0, + pds_temp_size, + PVRX(VDMCTRL_PDS_STATE0_PDS_TEMP_SIZE_UNIT_SIZE), + "bytes"); + pvr_dump_field_member_u32_scaled_units( + base_ctx, + &state0, + pds_data_size, + PVRX(VDMCTRL_PDS_STATE0_PDS_DATA_SIZE_UNIT_SIZE), + "bytes"); + + pvr_dump_field_member_addr(base_ctx, &state1, pds_data_addr); + pvr_dump_field_member_enum(base_ctx, + &state1, + sd_type, + pvr_cmd_enum_to_str(VDMCTRL_SD_TYPE)); + pvr_dump_field_member_enum(base_ctx, + &state1, + sd_next_type, + pvr_cmd_enum_to_str(VDMCTRL_SD_TYPE)); + + pvr_dump_field_member_addr(base_ctx, &state2, pds_code_addr); + + ret = true; + +end_pop_ctx: + pvr_dump_csb_block_ctx_pop(&ctx); + +end_out: + return ret; +} + +static bool +print_block_vdmctrl_vdm_state_update(struct pvr_dump_csb_ctx *const csb_ctx) +{ + struct pvr_dump_csb_block_ctx ctx; + struct pvr_dump_ctx *const base_ctx = &ctx.base.base; + bool ret = false; + + struct PVRX(VDMCTRL_VDM_STATE0) state0; + struct PVRX(VDMCTRL_VDM_STATE1) state1; + struct PVRX(VDMCTRL_VDM_STATE2) state2; + struct PVRX(VDMCTRL_VDM_STATE3) state3; + struct PVRX(VDMCTRL_VDM_STATE4) state4; + struct PVRX(VDMCTRL_VDM_STATE5) state5; + + if (!pvr_dump_csb_block_ctx_push(&ctx, csb_ctx, "VDM_STATE_UPDATE")) + goto end_out; + + if (!pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_VDM_STATE0, &state0)) + goto end_pop_ctx; + + if (state0.cut_index_present) { + if (!pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_VDM_STATE1, &state1)) + goto end_pop_ctx; + + pvr_dump_field_member_x32(base_ctx, &state1, cut_index, 8); + } else { + pvr_dump_field_member_not_present(base_ctx, &state1, cut_index); + } + + if (state0.vs_data_addr_present) { + if (!pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_VDM_STATE2, &state2)) + goto end_pop_ctx; + + pvr_dump_field_member_addr(base_ctx, &state2, vs_pds_data_base_addr); + } else { + pvr_dump_field_member_not_present(base_ctx, + &state2, + vs_pds_data_base_addr); + } + + if (state0.vs_other_present) { + if (!pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_VDM_STATE3, &state3) || + !pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_VDM_STATE4, &state4) || + !pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_VDM_STATE5, &state5)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_addr(base_ctx, &state3, vs_pds_code_base_addr); + + pvr_dump_field_member_u32_scaled_units( + base_ctx, + &state4, + vs_output_size, + PVRX(VDMCTRL_VDM_STATE4_VS_OUTPUT_SIZE_UNIT_SIZE), + "bytes"); + + pvr_dump_field_member_u32_zero(base_ctx, &state5, vs_max_instances, 32); + pvr_dump_field_member_u32_scaled_units( + base_ctx, + &state5, + vs_usc_common_size, + PVRX(VDMCTRL_VDM_STATE5_VS_USC_COMMON_SIZE_UNIT_SIZE), + "bytes"); + pvr_dump_field_member_u32_scaled_units( + base_ctx, + &state5, + vs_usc_unified_size, + PVRX(VDMCTRL_VDM_STATE5_VS_USC_UNIFIED_SIZE_UNIT_SIZE), + "bytes"); + pvr_dump_field_member_u32_scaled_units( + base_ctx, + &state5, + vs_pds_temp_size, + PVRX(VDMCTRL_VDM_STATE5_VS_PDS_TEMP_SIZE_UNIT_SIZE), + "bytes"); + pvr_dump_field_member_u32_scaled_units( + base_ctx, + &state5, + vs_pds_data_size, + PVRX(VDMCTRL_VDM_STATE5_VS_PDS_DATA_SIZE_UNIT_SIZE), + "bytes"); + } else { + pvr_dump_field_member_not_present(base_ctx, + &state3, + vs_pds_code_base_addr); + pvr_dump_field_member_not_present(base_ctx, &state4, vs_output_size); + pvr_dump_field_member_not_present(base_ctx, &state5, vs_max_instances); + pvr_dump_field_member_not_present(base_ctx, &state5, vs_usc_common_size); + pvr_dump_field_member_not_present(base_ctx, &state5, vs_usc_unified_size); + pvr_dump_field_member_not_present(base_ctx, &state5, vs_pds_temp_size); + pvr_dump_field_member_not_present(base_ctx, &state5, vs_pds_data_size); + } + + pvr_dump_field_member_bool(base_ctx, &state0, ds_present); + pvr_dump_field_member_bool(base_ctx, &state0, gs_present); + pvr_dump_field_member_bool(base_ctx, &state0, hs_present); + pvr_dump_field_member_u32_offset(base_ctx, &state0, cam_size, 1); + pvr_dump_field_member_enum( + base_ctx, + &state0, + uvs_scratch_size_select, + pvr_cmd_enum_to_str(VDMCTRL_UVS_SCRATCH_SIZE_SELECT)); + pvr_dump_field_member_bool(base_ctx, &state0, cut_index_enable); + pvr_dump_field_member_bool(base_ctx, &state0, tess_enable); + pvr_dump_field_member_bool(base_ctx, &state0, gs_enable); + pvr_dump_field_member_enum(base_ctx, + &state0, + flatshade_control, + pvr_cmd_enum_to_str(VDMCTRL_FLATSHADE_CONTROL)); + pvr_dump_field_member_bool(base_ctx, &state0, generate_primitive_id); + + ret = true; + +end_pop_ctx: + pvr_dump_csb_block_ctx_pop(&ctx); + +end_out: + return ret; +} + +static bool +print_block_vdmctrl_index_list(struct pvr_dump_csb_ctx *const csb_ctx, + const struct pvr_device_info *const dev_info) +{ + struct pvr_dump_csb_block_ctx ctx; + struct pvr_dump_ctx *const base_ctx = &ctx.base.base; + bool ret = false; + + struct PVRX(VDMCTRL_INDEX_LIST0) index_list0; + struct PVRX(VDMCTRL_INDEX_LIST1) index_list1; + struct PVRX(VDMCTRL_INDEX_LIST2) index_list2; + struct PVRX(VDMCTRL_INDEX_LIST3) index_list3; + struct PVRX(VDMCTRL_INDEX_LIST4) index_list4; + struct PVRX(VDMCTRL_INDEX_LIST5) index_list5; + struct PVRX(VDMCTRL_INDEX_LIST6) index_list6; + struct PVRX(VDMCTRL_INDEX_LIST7) index_list7; + struct PVRX(VDMCTRL_INDEX_LIST8) index_list8; + struct PVRX(VDMCTRL_INDEX_LIST9) index_list9; + + if (!pvr_dump_csb_block_ctx_push(&ctx, csb_ctx, "INDEX_LIST")) + goto end_out; + + if (!pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_INDEX_LIST0, &index_list0)) + goto end_pop_ctx; + + if (PVR_HAS_FEATURE(dev_info, vdm_degenerate_culling)) { + pvr_dump_field_member_bool(base_ctx, &index_list0, degen_cull_enable); + } else { + pvr_dump_field_member_needs_feature(base_ctx, + &index_list0, + degen_cull_enable, + vdm_degenerate_culling); + } + + pvr_dump_field_member_enum(base_ctx, + &index_list0, + index_size, + pvr_cmd_enum_to_str(VDMCTRL_INDEX_SIZE)); + pvr_dump_field_member_u32_offset(base_ctx, &index_list0, patch_count, 1); + pvr_dump_field_member_enum(base_ctx, + &index_list0, + primitive_topology, + pvr_cmd_enum_to_str(VDMCTRL_PRIMITIVE_TOPOLOGY)); + + if (index_list0.index_addr_present) { + if (!pvr_dump_csb_block_take_packed(&ctx, + VDMCTRL_INDEX_LIST1, + &index_list1)) { + goto end_pop_ctx; + } + + pvr_dump_field_addr_split(base_ctx, + "index_base_addr", + index_list0.index_base_addrmsb, + index_list1.index_base_addrlsb); + } else { + pvr_dump_field_not_present(base_ctx, "index_base_addr"); + } + + if (index_list0.index_count_present) { + if (!pvr_dump_csb_block_take_packed(&ctx, + VDMCTRL_INDEX_LIST2, + &index_list2)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_u32(base_ctx, &index_list2, index_count); + } else { + pvr_dump_field_member_not_present(base_ctx, &index_list2, index_count); + } + + if (index_list0.index_instance_count_present) { + if (!pvr_dump_csb_block_take_packed(&ctx, + VDMCTRL_INDEX_LIST3, + &index_list3)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_u32_offset(base_ctx, + &index_list3, + instance_count, + 1); + } else { + pvr_dump_field_member_not_present(base_ctx, &index_list3, instance_count); + } + + if (index_list0.index_offset_present) { + if (!pvr_dump_csb_block_take_packed(&ctx, + VDMCTRL_INDEX_LIST4, + &index_list4)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_u32(base_ctx, &index_list4, index_offset); + } else { + pvr_dump_field_member_not_present(base_ctx, &index_list4, index_offset); + } + + if (index_list0.start_present) { + if (!pvr_dump_csb_block_take_packed(&ctx, + VDMCTRL_INDEX_LIST5, + &index_list5) || + !pvr_dump_csb_block_take_packed(&ctx, + VDMCTRL_INDEX_LIST6, + &index_list6)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_u32(base_ctx, &index_list5, start_index); + pvr_dump_field_member_u32(base_ctx, &index_list6, start_instance); + } else { + pvr_dump_field_member_not_present(base_ctx, &index_list5, start_index); + pvr_dump_field_member_not_present(base_ctx, &index_list6, start_instance); + } + + if (index_list0.indirect_addr_present) { + if (!pvr_dump_csb_block_take_packed(&ctx, + VDMCTRL_INDEX_LIST7, + &index_list7) || + !pvr_dump_csb_block_take_packed(&ctx, + VDMCTRL_INDEX_LIST8, + &index_list8)) { + goto end_pop_ctx; + } + + pvr_dump_field_addr_split(base_ctx, + "indirect_base_addr", + index_list7.indirect_base_addrmsb, + index_list8.indirect_base_addrlsb); + } else { + pvr_dump_field_not_present(base_ctx, "indirect_base_addr"); + } + + if (index_list0.split_count_present) { + if (!pvr_dump_csb_block_take_packed(&ctx, + VDMCTRL_INDEX_LIST9, + &index_list9)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_u32(base_ctx, &index_list9, split_count); + } else { + pvr_dump_field_member_not_present(base_ctx, &index_list9, split_count); + } + + ret = true; + +end_pop_ctx: + pvr_dump_csb_block_ctx_pop(&ctx); + +end_out: + return ret; +} + +static bool +print_block_vdmctrl_stream_link(struct pvr_dump_csb_ctx *const csb_ctx) +{ + struct pvr_dump_csb_block_ctx ctx; + struct pvr_dump_ctx *const base_ctx = &ctx.base.base; + bool ret = false; + + struct PVRX(VDMCTRL_STREAM_LINK0) link0; + struct PVRX(VDMCTRL_STREAM_LINK1) link1; + + if (!pvr_dump_csb_block_ctx_push(&ctx, csb_ctx, "STREAM_LINK")) + goto end_out; + + if (!pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_STREAM_LINK0, &link0) || + !pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_STREAM_LINK1, &link1)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_bool(base_ctx, &link0, with_return); + + if (link0.compare_present) { + pvr_dump_field_member_u32(base_ctx, &link0, compare_mode); + pvr_dump_field_member_u32(base_ctx, &link0, compare_data); + } else { + pvr_dump_field_member_not_present(base_ctx, &link0, compare_mode); + pvr_dump_field_member_not_present(base_ctx, &link0, compare_data); + } + + pvr_dump_field_addr_split(base_ctx, + "link_addr", + link0.link_addrmsb, + link1.link_addrlsb); + + ret = true; + +end_pop_ctx: + pvr_dump_csb_block_ctx_pop(&ctx); + +end_out: + return ret; +} + +static bool +print_block_vdmctrl_stream_return(struct pvr_dump_csb_ctx *const csb_ctx) +{ + struct pvr_dump_csb_block_ctx ctx; + struct pvr_dump_ctx *const base_ctx = &ctx.base.base; + bool ret = false; + + struct PVRX(VDMCTRL_STREAM_RETURN) return_; + + if (!pvr_dump_csb_block_ctx_push(&ctx, csb_ctx, "STREAM_RETURN")) + goto end_out; + + if (!pvr_dump_csb_block_take_packed(&ctx, VDMCTRL_STREAM_RETURN, &return_)) + goto end_pop_ctx; + + pvr_dump_field_no_fields(base_ctx); + + ret = true; + +end_pop_ctx: + pvr_dump_csb_block_ctx_pop(&ctx); + +end_out: + return ret; +} + +static bool +print_block_vdmctrl_stream_terminate(struct pvr_dump_csb_ctx *const csb_ctx) +{ + struct pvr_dump_csb_block_ctx ctx; + struct pvr_dump_ctx *const base_ctx = &ctx.base.base; + bool ret = false; + + struct PVRX(VDMCTRL_STREAM_TERMINATE) terminate; + + if (!pvr_dump_csb_block_ctx_push(&ctx, csb_ctx, "TERMINATE")) + goto end_out; + + if (!pvr_dump_csb_block_take_packed(&ctx, + VDMCTRL_STREAM_TERMINATE, + &terminate)) { + goto end_pop_ctx; + } + + pvr_dump_field_member_bool(base_ctx, &terminate, context); + + ret = true; + +end_pop_ctx: + pvr_dump_csb_block_ctx_pop(&ctx); + +end_out: + return ret; +} + +/****************************************************************************** + Buffer printers + ******************************************************************************/ + +static bool print_cdmctrl_buffer(struct pvr_dump_buffer_ctx *const parent_ctx) +{ + struct pvr_dump_csb_ctx ctx; + bool ret = true; + + /* All blocks contain a block_type member in the first word at the same + * position. We could unpack any block to pick out this discriminant field, + * but this one has been chosen because it's only one word long. + */ + STATIC_ASSERT(pvr_cmd_length(CDMCTRL_STREAM_TERMINATE) == 1); + + if (!pvr_dump_csb_ctx_push(&ctx, parent_ctx)) + return false; + + do { + enum PVRX(CDMCTRL_BLOCK_TYPE) block_type; + const uint32_t *next_word; + + next_word = pvr_dump_buffer_peek(&ctx.base, sizeof(*next_word)); + if (!next_word) { + ret = false; + goto end_pop_ctx; + } + + block_type = + pvr_csb_unpack(next_word, CDMCTRL_STREAM_TERMINATE).block_type; + switch (block_type) { + case PVRX(CDMCTRL_BLOCK_TYPE_COMPUTE_KERNEL): + ret = print_block_cdmctrl_kernel(&ctx); + break; + + case PVRX(CDMCTRL_BLOCK_TYPE_STREAM_LINK): + ret = print_block_cdmctrl_stream_link(&ctx); + break; + + case PVRX(CDMCTRL_BLOCK_TYPE_STREAM_TERMINATE): + ret = print_block_cdmctrl_stream_terminate(&ctx); + break; + + default: + pvr_dump_buffer_print_header_line( + &ctx.base, + "", + block_type); + ret = false; + break; + } + + if (block_type == PVRX(CDMCTRL_BLOCK_TYPE_STREAM_TERMINATE)) + break; + } while (ret); + +end_pop_ctx: + pvr_dump_csb_ctx_pop(&ctx, true); + + return ret; +} + +static bool print_vdmctrl_buffer(struct pvr_dump_buffer_ctx *const parent_ctx, + struct pvr_device *const device) +{ + struct pvr_dump_csb_ctx ctx; + bool ret = true; + + /* All blocks contain a block_type member in the first word at the same + * position. We could unpack any block to pick out this discriminant field, + * but this one has been chosen because it's only one word long. + */ + STATIC_ASSERT(pvr_cmd_length(VDMCTRL_STREAM_RETURN) == 1); + + if (!pvr_dump_csb_ctx_push(&ctx, parent_ctx)) + return false; + + do { + enum PVRX(VDMCTRL_BLOCK_TYPE) block_type; + const uint32_t *next_word; + + next_word = pvr_dump_buffer_peek(&ctx.base, sizeof(*next_word)); + if (!next_word) { + ret = false; + goto end_pop_ctx; + } + + block_type = pvr_csb_unpack(next_word, VDMCTRL_STREAM_RETURN).block_type; + switch (block_type) { + case PVRX(VDMCTRL_BLOCK_TYPE_PPP_STATE_UPDATE): + ret = print_block_vdmctrl_ppp_state_update(&ctx, device); + break; + + case PVRX(VDMCTRL_BLOCK_TYPE_PDS_STATE_UPDATE): + ret = print_block_vdmctrl_pds_state_update(&ctx); + break; + + case PVRX(VDMCTRL_BLOCK_TYPE_VDM_STATE_UPDATE): + ret = print_block_vdmctrl_vdm_state_update(&ctx); + break; + + case PVRX(VDMCTRL_BLOCK_TYPE_INDEX_LIST): + ret = print_block_vdmctrl_index_list(&ctx, &device->pdevice->dev_info); + break; + + case PVRX(VDMCTRL_BLOCK_TYPE_STREAM_LINK): + ret = print_block_vdmctrl_stream_link(&ctx); + break; + + case PVRX(VDMCTRL_BLOCK_TYPE_STREAM_RETURN): + ret = print_block_vdmctrl_stream_return(&ctx); + break; + + case PVRX(VDMCTRL_BLOCK_TYPE_STREAM_TERMINATE): + ret = print_block_vdmctrl_stream_terminate(&ctx); + break; + + default: + pvr_dump_buffer_print_header_line( + &ctx.base, + "", + block_type); + ret = false; + break; + } + + if (block_type == PVRX(VDMCTRL_BLOCK_TYPE_STREAM_TERMINATE)) + break; + } while (ret); + +end_pop_ctx: + pvr_dump_csb_ctx_pop(&ctx, true); + + return ret; +} + +/****************************************************************************** + Top-level dumping + ******************************************************************************/ + +static bool dump_first_buffer(struct pvr_dump_buffer_ctx *const ctx, + const enum pvr_cmd_stream_type stream_type, + struct pvr_device *const device) +{ + bool ret = false; + + pvr_dump_mark_section(&ctx->base, "First buffer content"); + switch (stream_type) { + case PVR_CMD_STREAM_TYPE_GRAPHICS: + ret = print_vdmctrl_buffer(ctx, device); + break; + + case PVR_CMD_STREAM_TYPE_COMPUTE: + ret = print_cdmctrl_buffer(ctx); + break; + + default: + unreachable("Unknown stream type"); + } + + if (!ret) + pvr_dump_println(&ctx->base, + "", + ctx->ptr - ctx->initial_ptr); + + pvr_dump_buffer_restart(ctx); + pvr_dump_mark_section(&ctx->base, "First buffer hexdump"); + return pvr_dump_buffer_hex(ctx, 0); +} + +/****************************************************************************** + Public functions + ******************************************************************************/ + +void pvr_csb_dump(const struct pvr_csb *const csb, + const uint32_t frame_num, + const uint32_t job_num) +{ + const uint32_t nr_bos = list_length(&csb->pvr_bo_list); + struct pvr_device *const device = csb->device; + + struct pvr_dump_bo_ctx first_bo_ctx; + struct pvr_dump_ctx root_ctx; + + pvr_dump_begin(&root_ctx, stderr, "CONTROL STREAM DUMP", 6); + + pvr_dump_field_u32(&root_ctx, "Frame num", frame_num); + pvr_dump_field_u32(&root_ctx, "Job num", job_num); + pvr_dump_field_enum(&root_ctx, "Status", csb->status, vk_Result_to_str); + pvr_dump_field_enum(&root_ctx, + "Stream type", + csb->stream_type, + pvr_cmd_stream_type_to_str); + + if (nr_bos <= 1) { + pvr_dump_field_u32(&root_ctx, "Nr of BOs", nr_bos); + } else { + /* TODO: Implement multi-buffer dumping. */ + pvr_dump_field_computed(&root_ctx, + "Nr of BOs", + "%" PRIu32, + "only the first buffer will be dumped", + nr_bos); + } + + if (nr_bos == 0) + goto end_dump; + + pvr_dump_mark_section(&root_ctx, "Buffer objects"); + pvr_bo_list_dump(&root_ctx, &csb->pvr_bo_list, nr_bos); + + if (!pvr_dump_bo_ctx_push( + &first_bo_ctx, + &root_ctx, + device, + list_first_entry(&csb->pvr_bo_list, struct pvr_bo, link))) { + pvr_dump_mark_section(&root_ctx, "First buffer"); + pvr_dump_println(&root_ctx, ""); + goto end_dump; + } + + dump_first_buffer(&first_bo_ctx.base, csb->stream_type, device); + + pvr_dump_bo_ctx_pop(&first_bo_ctx); + +end_dump: + pvr_dump_end(&root_ctx); +} diff --git a/src/imagination/vulkan/pvr_job_compute.c b/src/imagination/vulkan/pvr_job_compute.c index ef578545f2b..1c71c4309c9 100644 --- a/src/imagination/vulkan/pvr_job_compute.c +++ b/src/imagination/vulkan/pvr_job_compute.c @@ -27,6 +27,7 @@ #include #include "pvr_csb.h" +#include "pvr_debug.h" #include "pvr_job_common.h" #include "pvr_job_context.h" #include "pvr_job_compute.h" @@ -213,6 +214,12 @@ VkResult pvr_compute_job_submit(struct pvr_compute_ctx *ctx, stage_flags, &submit_info); + if (PVR_IS_DEBUG_SET(DUMP_CONTROL_STREAM)) { + pvr_csb_dump(&sub_cmd->control_stream, + submit_info.frame_num, + submit_info.job_num); + } + return device->ws->ops->compute_submit(ctx->ws_ctx, &submit_info, &device->pdevice->dev_info, diff --git a/src/imagination/vulkan/pvr_job_render.c b/src/imagination/vulkan/pvr_job_render.c index 03cb0140f1b..8ba24c9f40a 100644 --- a/src/imagination/vulkan/pvr_job_render.c +++ b/src/imagination/vulkan/pvr_job_render.c @@ -30,6 +30,7 @@ #include "hwdef/rogue_hw_utils.h" #include "pvr_bo.h" #include "pvr_csb.h" +#include "pvr_debug.h" #include "pvr_csb_enum_helpers.h" #include "pvr_debug.h" #include "pvr_job_common.h" @@ -1677,6 +1678,20 @@ VkResult pvr_render_job_submit(struct pvr_render_ctx *ctx, stage_flags, &submit_info); + if (PVR_IS_DEBUG_SET(DUMP_CONTROL_STREAM)) { + /* FIXME: This isn't an ideal method of accessing the information we + * need, but it's considered good enough for a debug code path. It can be + * streamlined and made more correct if/when pvr_render_job becomes a + * subclass of pvr_sub_cmd. + */ + const struct pvr_sub_cmd *sub_cmd = + container_of(job, const struct pvr_sub_cmd, gfx.job); + + pvr_csb_dump(&sub_cmd->gfx.control_stream, + submit_info.frame_num, + submit_info.job_num); + } + result = device->ws->ops->render_submit(ctx->ws_ctx, &submit_info, &device->pdevice->dev_info,