blob: cd1f2b549cc3bd31de3352b4133108f023af075e [file] [log] [blame]
/*
* 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 <inttypes.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#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_msg(ctx, prefix, ret, format, args...) \
({ \
bool _ret = (ret); \
struct pvr_dump_ctx *_ctx = (ctx); \
pvr_dump_println(_ctx, "<!" prefix "! " format ">", ##args); \
if (!_ret) \
_ctx->ok = _ret; \
_ret; \
})
#define pvr_dump_error(ctx, format, args...) \
pvr_dump_msg(ctx, "ERROR", false, format, ##args)
#define pvr_dump_warn(ctx, format, args...) \
pvr_dump_msg(ctx, "WARN", true, format, ##args)
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_bytes);
static inline void __pvr_dump_buffer_advance(struct pvr_dump_buffer_ctx *ctx,
const uint64_t nr_bytes)
{
ctx->ptr = (uint8_t *)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 void __pvr_dump_buffer_rewind(struct pvr_dump_buffer_ctx *ctx,
const uint32_t nr_bytes)
{
ctx->ptr = (uint8_t *)ctx->ptr - nr_bytes;
ctx->remaining_size += nr_bytes;
}
static inline bool pvr_dump_buffer_rewind(struct pvr_dump_buffer_ctx *ctx,
const uint32_t nr_bytes)
{
if (!ctx->base.ok || !pvr_dump_ctx_require_top(&ctx->base))
return false;
if (nr_bytes > ctx->capacity - ctx->remaining_size)
return pvr_dump_error(&ctx->base, "rewound past start of context buffer");
__pvr_dump_buffer_rewind(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, "<!ERROR!>", "<" 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));
}
static inline void pvr_dump_field_u64(struct pvr_dump_ctx *const ctx,
const char *const name,
const uint64_t value)
{
pvr_dump_field(ctx, name, "%" PRIu64, value);
}
static inline void pvr_dump_field_u64_units(struct pvr_dump_ctx *const ctx,
const char *const name,
const uint64_t value,
const char *const units)
{
pvr_dump_field(ctx, name, "%" PRIu64 " %s", value, units);
}
/*****************************************************************************
Field printers: floating point
*****************************************************************************/
static inline void pvr_dump_field_f32(struct pvr_dump_ctx *const ctx,
const char *const name,
const float value)
{
pvr_dump_field_computed(ctx, name, "%f", "0x%08" PRIx32, value, fui(value));
}
/*****************************************************************************
Field printers: fixed point
*****************************************************************************/
/* clang-format off */
static const char *const __fixed_frac_str_table_4[1 << 4] = {
"0", "0625", "125", "1875", "25", "3125", "375", "4375",
"5", "5625", "625", "6875", "75", "8125", "875", "9375",
};
/* clang-format on */
static inline void pvr_dump_field_uq4_4(struct pvr_dump_ctx *const ctx,
const char *const name,
const uint32_t raw_value)
{
const uint32_t int_part = (raw_value & BITFIELD_RANGE(4, 4)) >> 4;
const uint32_t frac_part = raw_value & BITFIELD_MASK(4);
pvr_dump_field_computed(ctx,
name,
"%" PRIu32 ".%s",
"0x%02" PRIx32, /* Or %0*x where *=(nr_bits+3)/4 */
int_part,
__fixed_frac_str_table_4[frac_part],
raw_value & BITFIELD_MASK(8));
}
static inline void pvr_dump_field_uq4_4_offset(struct pvr_dump_ctx *const ctx,
const char *const name,
const uint32_t raw_value,
const uint32_t raw_offset)
{
const uint32_t raw_offset_value = raw_value + raw_offset;
const uint32_t int_part = (raw_offset_value & BITFIELD_RANGE(4, 4)) >> 4;
const uint32_t frac_part = raw_offset_value & BITFIELD_MASK(4);
pvr_dump_field_computed(ctx,
name,
"%" PRIu32 ".%s",
"0x%02" PRIx32 " + 0x%02" PRIx32,
int_part,
__fixed_frac_str_table_4[frac_part],
raw_value & BITFIELD_MASK(8),
raw_offset);
}
/*****************************************************************************
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, "<null>");
}
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);
}
static inline void pvr_dump_field_addr_offset(struct pvr_dump_ctx *const ctx,
const char *const name,
const pvr_dev_addr_t value,
const pvr_dev_addr_t base)
{
pvr_dump_field_computed(ctx,
name,
PVR_DEV_ADDR_FMT,
PVR_DEV_ADDR_FMT " + " PVR_DEV_ADDR_FMT,
PVR_DEV_ADDR_OFFSET(base, value.addr).addr,
base.addr,
value.addr);
}
/*****************************************************************************
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 = "<unknown>"; \
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: string
*****************************************************************************/
static inline void pvr_dump_field_string(struct pvr_dump_ctx *const ctx,
const char *const name,
const char *const value)
{
pvr_dump_field(ctx, name, "%s", value);
}
/*****************************************************************************
Field printers: not present
*****************************************************************************/
static inline void pvr_dump_field_no_fields(struct pvr_dump_ctx *const ctx)
{
pvr_dump_println(ctx, "<no fields>");
}
static inline void pvr_dump_field_not_present(struct pvr_dump_ctx *const ctx,
const char *const name)
{
pvr_dump_field(ctx, name, "<not present>");
}
/*****************************************************************************
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_u64(ctx, compound, member) \
pvr_dump_field_u64(ctx, #member, (compound)->member)
#define pvr_dump_field_member_u64_units(ctx, compound, member, units) \
pvr_dump_field_u64_units(ctx, #member, (compound)->member, units)
#define pvr_dump_field_member_f32(ctx, compound, member) \
pvr_dump_field_f32(ctx, #member, (compound)->member)
#define pvr_dump_field_member_uq4_4(ctx, compound, member) \
pvr_dump_field_uq4_4(ctx, #member, (compound)->member)
#define pvr_dump_field_member_uq4_4_offset(ctx, compound, member, raw_offset) \
pvr_dump_field_uq4_4_offset(ctx, #member, (compound)->member, raw_offset)
#define pvr_dump_field_member_addr(ctx, compound, member) \
pvr_dump_field_addr(ctx, #member, (compound)->member)
#define pvr_dump_field_member_addr_offset(ctx, compound, member, base) \
pvr_dump_field_addr_offset(ctx, #member, (compound)->member, base)
#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)
#define pvr_dump_field_member_string(ctx, compound, member) \
pvr_dump_field_string(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 */