Commit bba61cef authored by Rob Clark's avatar Rob Clark 💬 Committed by Marge Bot
Browse files

freedreno/afuc: Add emulator mode to afuc-disasm



This is an (at least somewhat complete) logical emulator of the a6xx SQE
that lets us step through firmware execution (bootstrap, cmdstream pkt
handling, etc).  It lets us poke at various fw visible state and run
through pm4 packet(s) to better understand what the fw is doing when it
handles various packets.
Signed-off-by: Rob Clark's avatarRob Clark <robdclark@chromium.org>
Part-of: <!10944>
parent 745dad04
......@@ -24,6 +24,8 @@
#ifndef _AFUC_H_
#define _AFUC_H_
#include <stdbool.h>
#include "util/macros.h"
/*
......@@ -164,6 +166,16 @@ typedef union PACKED {
} alu;
struct PACKED {
uint32_t uimm : 12;
/* TODO this needs to be confirmed:
*
* flags:
* 0x4 - post-increment src2 by uimm (need to confirm this is also
* true for load/cread). TBD whether, when used in conjunction
* with @LOAD_STORE_HI, 32b rollover works properly.
*
* other values tbd, also need to confirm if different bits can be
* set together (I don't see examples of this in existing fw)
*/
uint32_t flags : 4;
uint32_t src1 : 5; /* dst (cread) or src (cwrite) register */
uint32_t src2 : 5; /* read or write address is src2+uimm */
......@@ -218,4 +230,9 @@ afuc_set_opc(afuc_instr *ai, afuc_opc opc, bool rep)
}
}
void print_src(unsigned reg);
void print_dst(unsigned reg);
void print_control_reg(uint32_t id);
void print_pipe_reg(uint32_t id);
#endif /* _AFUC_H_ */
......@@ -39,6 +39,7 @@
#include "afuc.h"
#include "util.h"
#include "emu.h"
static int gpuver;
......@@ -48,6 +49,9 @@ static int gpuver;
*/
static bool verbose = false;
/* emulator mode: */
static bool emulator = false;
static void
print_gpu_reg(uint32_t regbase)
{
......@@ -64,7 +68,7 @@ print_gpu_reg(uint32_t regbase)
#define printerr(fmt, ...) afuc_printc(AFUC_ERR, fmt, ##__VA_ARGS__)
#define printlbl(fmt, ...) afuc_printc(AFUC_LBL, fmt, ##__VA_ARGS__)
static void
void
print_src(unsigned reg)
{
if (reg == REG_REM)
......@@ -79,7 +83,7 @@ print_src(unsigned reg)
printf("$%02x", reg);
}
static void
void
print_dst(unsigned reg)
{
if (reg == REG_REM)
......@@ -257,7 +261,7 @@ fxn_name(uint32_t offset)
return name;
}
static void
void
print_control_reg(uint32_t id)
{
char *name = afuc_control_reg_name(id);
......@@ -269,7 +273,7 @@ print_control_reg(uint32_t id)
}
}
static void
void
print_pipe_reg(uint32_t id)
{
char *name = afuc_pipe_reg_name(id);
......@@ -752,6 +756,20 @@ disasm(uint32_t *buf, int sizedwords)
}
}
if (emulator) {
struct emu state = {
.instrs = instrs,
.sizedwords = sizedwords,
};
emu_init(&state);
while (true) {
disasm_instr(instrs, state.gpr_regs.pc);
emu_step(&state);
}
}
/* print instructions: */
for (i = 0; i < jmptbl_start; i++) {
disasm_instr(instrs, i);
......@@ -784,7 +802,8 @@ usage(void)
"\tdisasm [-g GPUVER] [-v] [-c] filename.asm\n"
"\t\t-g - specify GPU version (5, etc)\n"
"\t\t-c - use colors\n"
"\t\t-v - verbose output\n");
"\t\t-v - verbose output\n"
"\t\t-e - emulator mode\n");
exit(2);
}
......@@ -798,7 +817,7 @@ main(int argc, char **argv)
int c, ret;
/* Argument parsing: */
while ((c = getopt(argc, argv, "g:vc")) != -1) {
while ((c = getopt(argc, argv, "g:vce")) != -1) {
switch (c) {
case 'g':
gpuver = atoi(optarg);
......@@ -809,6 +828,10 @@ main(int argc, char **argv)
case 'c':
colors = true;
break;
case 'e':
emulator = true;
verbose = true;
break;
default:
usage();
}
......@@ -830,6 +853,15 @@ main(int argc, char **argv)
}
}
/* a6xx is *mostly* a superset of a5xx, but some opcodes shuffle
* around, and behavior of special regs is a bit different. Right
* now we only bother to support the a6xx variant.
*/
if (emulator && (gpuver != 6)) {
fprintf(stderr, "Emulator only supported on a6xx!\n");
return 1;
}
ret = afuc_util_init(gpuver, colors);
if (ret < 0) {
usage();
......
/*
* Copyright © 2021 Google, Inc.
*
* 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 <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include "emu.h"
#include "util.h"
/*
* Emulation for draw-state (ie. CP_SET_DRAW_STATE) related control registers:
*/
EMU_CONTROL_REG(DRAW_STATE_SET);
EMU_CONTROL_REG(DRAW_STATE_SEL);
EMU_CONTROL_REG(DRAW_STATE_ACTIVE_BITMASK);
EMU_CONTROL_REG(DRAW_STATE_HDR);
EMU_CONTROL_REG(DRAW_STATE_BASE);
EMU_CONTROL_REG(SDS_BASE);
EMU_CONTROL_REG(SDS_DWORDS);
uint32_t
emu_get_draw_state_reg(struct emu *emu, unsigned n)
{
// TODO maybe we don't need to do anything here
return emu->control_regs.val[n];
}
void
emu_set_draw_state_reg(struct emu *emu, unsigned n, uint32_t val)
{
struct emu_draw_state *ds = &emu->draw_state;
unsigned cur_idx = emu_get_reg32(emu, &DRAW_STATE_SEL);
if (n == emu_reg_offset(&DRAW_STATE_SET)) {
if (ds->write_idx == 0) {
cur_idx = (val >> 24) & 0x1f;
ds->state[cur_idx].count = val & 0xffff;
ds->state[cur_idx].mode_mask = (val >> 20) & 0x7;
unsigned active_mask = emu_get_reg32(emu, &DRAW_STATE_ACTIVE_BITMASK);
active_mask |= (1 << cur_idx);
emu_set_reg32(emu, &DRAW_STATE_ACTIVE_BITMASK, active_mask);
emu_set_reg32(emu, &DRAW_STATE_SEL, cur_idx);
} else {
ds->state[cur_idx].base_lohi[ds->write_idx - 1] = val;
}
ds->write_idx = (ds->write_idx + 1) % 3;
} else if (n == emu_reg_offset(&DRAW_STATE_SEL)) {
emu_set_reg32(emu, &DRAW_STATE_HDR, ds->state[val].hdr);
emu_set_reg64(emu, &DRAW_STATE_BASE, ds->state[val].base);
/* It seems that SDS_BASE/SDS_DWORDS is also per draw-state group,
* and that when a new state-group is selected, SQE compares to
* the previous values to new DRAW_STATE_BASE & count to detect
* that new state has been appended to existing draw-state group:
*/
unsigned prev_idx = ds->prev_draw_state_sel;
ds->state[prev_idx].sds_base = emu_get_reg64(emu, &SDS_BASE);
ds->state[prev_idx].sds_dwords = emu_get_reg32(emu, &SDS_DWORDS);
emu_set_reg64(emu, &SDS_BASE, ds->state[val].sds_base);
emu_set_reg32(emu, &SDS_DWORDS, ds->state[val].sds_dwords);
ds->prev_draw_state_sel = val;
}
}
/*
* Copyright © 2021 Google, Inc.
*
* 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 <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include "emu.h"
#include "util.h"
/*
* Emulator Registers:
*
* Handles access to GPR, GPU, control, and pipe registers.
*/
static bool
is_draw_state_control_reg(unsigned n)
{
char *reg_name = afuc_control_reg_name(n);
if (!reg_name)
return false;
bool ret = !!strstr(reg_name, "DRAW_STATE");
free(reg_name);
return ret;
}
uint32_t
emu_get_control_reg(struct emu *emu, unsigned n)
{
assert(n < ARRAY_SIZE(emu->control_regs.val));
if (is_draw_state_control_reg(n))
return emu_get_draw_state_reg(emu, n);
return emu->control_regs.val[n];
}
void
emu_set_control_reg(struct emu *emu, unsigned n, uint32_t val)
{
EMU_CONTROL_REG(PACKET_TABLE_WRITE);
EMU_CONTROL_REG(PACKET_TABLE_WRITE_ADDR);
EMU_CONTROL_REG(REG_WRITE);
EMU_CONTROL_REG(REG_WRITE_ADDR);
assert(n < ARRAY_SIZE(emu->control_regs.val));
BITSET_SET(emu->control_regs.written, n);
emu->control_regs.val[n] = val;
/* Some control regs have special action on write: */
if (n == emu_reg_offset(&PACKET_TABLE_WRITE)) {
unsigned write_addr = emu_get_reg32(emu, &PACKET_TABLE_WRITE_ADDR);
assert(write_addr < ARRAY_SIZE(emu->jmptbl));
emu->jmptbl[write_addr] = val;
emu_set_reg32(emu, &PACKET_TABLE_WRITE_ADDR, write_addr + 1);
} else if (n == emu_reg_offset(&REG_WRITE)) {
uint32_t write_addr = emu_get_reg32(emu, &REG_WRITE_ADDR);
/* Upper bits seem like some flags, not part of the actual
* register offset.. not sure what they mean yet:
*/
uint32_t flags = write_addr >> 16;
write_addr &= 0xffff;
emu_set_gpu_reg(emu, write_addr++, val);
emu_set_reg32(emu, &REG_WRITE_ADDR, write_addr | (flags << 16));
} else if (is_draw_state_control_reg(n)) {
emu_set_draw_state_reg(emu, n, val);
}
}
static uint32_t
emu_get_pipe_reg(struct emu *emu, unsigned n)
{
assert(n < ARRAY_SIZE(emu->pipe_regs.val));
return emu->pipe_regs.val[n];
}
static void
emu_set_pipe_reg(struct emu *emu, unsigned n, uint32_t val)
{
EMU_PIPE_REG(NRT_DATA);
EMU_PIPE_REG(NRT_ADDR);
assert(n < ARRAY_SIZE(emu->pipe_regs.val));
BITSET_SET(emu->pipe_regs.written, n);
emu->pipe_regs.val[n] = val;
/* Some pipe regs have special action on write: */
if (n == emu_reg_offset(&NRT_DATA)) {
uintptr_t addr = emu_get_reg64(emu, &NRT_ADDR);
emu_mem_write_dword(emu, addr, val);
emu_set_reg64(emu, &NRT_ADDR, addr + 4);
}
}
static uint32_t
emu_get_gpu_reg(struct emu *emu, unsigned n)
{
if (n >= ARRAY_SIZE(emu->gpu_regs.val))
return 0;
assert(n < ARRAY_SIZE(emu->gpu_regs.val));
return emu->gpu_regs.val[n];
}
void
emu_set_gpu_reg(struct emu *emu, unsigned n, uint32_t val)
{
if (n >= ARRAY_SIZE(emu->gpu_regs.val))
return;
assert(n < ARRAY_SIZE(emu->gpu_regs.val));
BITSET_SET(emu->gpu_regs.written, n);
emu->gpu_regs.val[n] = val;
}
static bool
is_pipe_reg_addr(unsigned regoff)
{
return regoff > 0xffff;
}
static unsigned
get_reg_addr(struct emu *emu)
{
switch (emu->data_mode) {
case DATA_PIPE:
case DATA_ADDR: return REG_ADDR;
case DATA_USRADDR: return REG_USRADDR;
default:
unreachable("bad data_mode");
return 0;
}
}
/* Handle reads for special streaming regs: */
static uint32_t
emu_get_fifo_reg(struct emu *emu, unsigned n)
{
/* TODO the fifo regs are slurping out of a FIFO that the hw is filling
* in parallel.. we can use `struct emu_queue` to emulate what is actually
* happening more accurately
*/
if (n == REG_MEMDATA) {
/* $memdata */
EMU_CONTROL_REG(MEM_READ_DWORDS);
EMU_CONTROL_REG(MEM_READ_ADDR);
unsigned read_dwords = emu_get_reg32(emu, &MEM_READ_DWORDS);
uintptr_t read_addr = emu_get_reg64(emu, &MEM_READ_ADDR);
if (read_dwords > 0) {
emu_set_reg32(emu, &MEM_READ_DWORDS, read_dwords - 1);
emu_set_reg64(emu, &MEM_READ_ADDR, read_addr + 4);
}
return emu_mem_read_dword(emu, read_addr);
} else if (n == REG_REGDATA) {
/* $regdata */
EMU_CONTROL_REG(REG_READ_DWORDS);
EMU_CONTROL_REG(REG_READ_ADDR);
unsigned read_dwords = emu_get_reg32(emu, &REG_READ_DWORDS);
unsigned read_addr = emu_get_reg32(emu, &REG_READ_ADDR);
/* I think if the fw doesn't write REG_READ_DWORDS before
* REG_READ_ADDR, it just ends up with a single value written
* into the FIFO that $regdata is consuming from:
*/
if (read_dwords > 0) {
emu_set_reg32(emu, &REG_READ_DWORDS, read_dwords - 1);
emu_set_reg32(emu, &REG_READ_ADDR, read_addr + 1);
}
return emu_get_gpu_reg(emu, read_addr);
} else if (n == REG_DATA) {
/* $data */
do {
uint32_t rem = emu->gpr_regs.val[REG_REM];
assert(rem >= 0);
uint32_t val;
if (emu_queue_pop(&emu->roq, &val)) {
emu_set_gpr_reg(emu, REG_REM, --rem);
return val;
}
/* If FIFO is empty, prompt for more input: */
printf("FIFO empty, input a packet!\n");
emu->run_mode = false;
emu_main_prompt(emu);
} while (true);
} else {
unreachable("not a FIFO reg");
return 0;
}
}
static void
emu_set_fifo_reg(struct emu *emu, unsigned n, uint32_t val)
{
if ((n == REG_ADDR) || (n == REG_USRADDR)) {
emu->data_mode = (n == REG_ADDR) ? DATA_ADDR : DATA_USRADDR;
/* Treat these as normal register writes so we can see
* updated values in the output as we step thru the
* instructions:
*/
emu->gpr_regs.val[n] = val;
BITSET_SET(emu->gpr_regs.written, n);
if (is_pipe_reg_addr(val)) {
/* "void" pipe regs don't have a value to write, so just
* treat it as writing zero to the pipe reg:
*/
if (afuc_pipe_reg_is_void(val >> 24))
emu_set_pipe_reg(emu, val >> 24, 0);
emu->data_mode = DATA_PIPE;
}
} else if (n == REG_DATA) {
unsigned reg = get_reg_addr(emu);
unsigned regoff = emu->gpr_regs.val[reg];
if (is_pipe_reg_addr(regoff)) {
/* writes pipe registers: */
assert(!(regoff & 0xfbffff));
/* If b18 is set, don't auto-increment dest addr.. and if we
* do auto-increment, we only increment the high 8b
*
* Note that we bypass emu_set_gpr_reg() in this case because
* auto-incrementing isn't triggering a write to "void" pipe
* regs.
*/
if (!(regoff & 0x40000)) {
emu->gpr_regs.val[reg] = regoff + 0x01000000;
BITSET_SET(emu->gpr_regs.written, reg);
}
emu_set_pipe_reg(emu, regoff >> 24, val);
} else {
/* writes to gpu registers: */
emu_set_gpr_reg(emu, reg, regoff+1);
emu_set_gpu_reg(emu, regoff, val);
}
}
}
uint32_t
emu_get_gpr_reg(struct emu *emu, unsigned n)
{
assert(n < ARRAY_SIZE(emu->gpr_regs.val));
/* Handle special regs: */
switch (n) {
case 0x00:
return 0;
case REG_MEMDATA:
case REG_REGDATA:
case REG_DATA:
return emu_get_fifo_reg(emu, n);
default:
return emu->gpr_regs.val[n];
}
}
void
emu_set_gpr_reg(struct emu *emu, unsigned n, uint32_t val)
{
assert(n < ARRAY_SIZE(emu->gpr_regs.val));
switch (n) {
case REG_ADDR:
case REG_USRADDR:
case REG_DATA:
emu_set_fifo_reg(emu, n, val);
break;
default:
emu->gpr_regs.val[n] = val;
BITSET_SET(emu->gpr_regs.written, n);
break;
}
}
/*
* Control/pipe register accessor helpers:
*/
struct emu_reg_accessor {
unsigned (*get_offset)(const char *name);
uint32_t (*get)(struct emu *emu, unsigned n);
void (*set)(struct emu *emu, unsigned n, uint32_t val);
};
const struct emu_reg_accessor emu_control_accessor = {
.get_offset = afuc_control_reg,
.get = emu_get_control_reg,
.set = emu_set_control_reg,
};
const struct emu_reg_accessor emu_pipe_accessor = {
.get_offset = afuc_pipe_reg,
.get = emu_get_pipe_reg,
.set = emu_set_pipe_reg,
};
const struct emu_reg_accessor emu_gpu_accessor = {
.get_offset = afuc_gpu_reg,
.get = emu_get_gpu_reg,
.set = emu_set_gpu_reg,
};
unsigned
emu_reg_offset(struct emu_reg *reg)
{
if (reg->offset == ~0)
reg->offset = reg->accessor->get_offset(reg->name);
return reg->offset;
}
uint32_t
emu_get_reg32(struct emu *emu, struct emu_reg *reg)
{
return reg->accessor->get(emu, emu_reg_offset(reg));
}
uint64_t
emu_get_reg64(struct emu *emu, struct emu_reg *reg)
{
uint64_t val = reg->accessor->get(emu, emu_reg_offset(reg) + 1);
val <<= 32;
val |= reg->accessor->get(emu, emu_reg_offset(reg));
return val;
}
void
emu_set_reg32(struct emu *emu, struct emu_reg *reg, uint32_t val)
{
reg->accessor->set(emu, emu_reg_offset(reg), val);
}
void
emu_set_reg64(struct emu *emu, struct emu_reg *reg, uint64_t val)
{
reg->accessor->set(emu, emu_reg_offset(reg), val);
reg->accessor->set(emu, emu_reg_offset(reg) + 1, val >> 32);
}
/*
* Copyright © 2021 Google, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a