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 <robdclark@chromium.org>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/10944>
This commit is contained in:
Rob Clark 2021-05-16 13:09:29 -07:00 committed by Marge Bot
parent 745dad0446
commit bba61cef38
10 changed files with 1829 additions and 7 deletions

View File

@ -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_ */

View File

@ -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();

View File

@ -0,0 +1,91 @@
/*
* 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;
}
}

View File

@ -0,0 +1,371 @@
/*
* 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);
}

531
src/freedreno/afuc/emu-ui.c Normal file
View File

@ -0,0 +1,531 @@
/*
* 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 <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include "freedreno_pm4.h"
#include "emu.h"
#include "util.h"
/*
* Emulator User Interface:
*
* Handles the user prompts and input parsing.
*/
static void
clear_line(void)
{
if (!isatty(STDOUT_FILENO))
return;
printf("\r \r");
}
static int
readchar(void)
{
static struct termios saved_termios, unbuffered_termios;
int c;
fflush(stdout);
tcgetattr(STDIN_FILENO, &saved_termios);
unbuffered_termios = saved_termios;
cfmakeraw(&unbuffered_termios);
tcsetattr(STDIN_FILENO, TCSANOW, &unbuffered_termios);
do {
c = getchar();
} while (isspace(c));
tcsetattr(STDIN_FILENO, TCSANOW, &saved_termios);
/* TODO, read from script until EOF and then read from stdin: */
if (c == -1)
exit(0);
return c;
}
static const char *
extract_string(char **buf)
{
char *p = *buf;
/* eat any leading whitespace: */
while (*p && isspace(*p))
p++;
if (!*p)
return NULL;
char *ret = p;
/* skip to next whitespace: */
while (*p && !isspace(*p))
p++;
if (*p)
*p = '\0';
*buf = ++p;
return ret;
}
static size_t
readline(char **p)
{
static char *buf;
static size_t n;
ssize_t ret = getline(&buf, &n, stdin);
if (ret < 0)
return ret;
*p = buf;
return 0;
}
static ssize_t
read_two_values(const char **val1, const char **val2)
{
char *p;
ssize_t ret = readline(&p);
if (ret < 0)
return ret;
*val1 = extract_string(&p);
*val2 = extract_string(&p);
return 0;
}
static ssize_t
read_one_value(const char **val)
{
char *p;
ssize_t ret = readline(&p);
if (ret < 0)
return ret;
*val = extract_string(&p);
return 0;
}
static void
dump_gpr_register(struct emu *emu, unsigned n)
{
printf(" GPR: ");
print_dst(n);
printf(": ");
if (BITSET_TEST(emu->gpr_regs.written, n)) {
printdelta("%08x\n", emu->gpr_regs.val[n]);
} else {
printf("%08x\n", emu->gpr_regs.val[n]);
}
}
static void
dump_gpr_registers(struct emu *emu)
{
for (unsigned i = 0; i < ARRAY_SIZE(emu->gpr_regs.val); i++) {
dump_gpr_register(emu, i);
}
}
static void
dump_gpu_register(struct emu *emu, unsigned n)
{
printf(" GPU: ");
char *name = afuc_gpu_reg_name(n);
if (name) {
printf("%s", name);
free(name);
} else {
printf("0x%04x", n);
}
printf(": ");
if (BITSET_TEST(emu->gpu_regs.written, n)) {
printdelta("%08x\n", emu->gpu_regs.val[n]);
} else {
printf("%08x\n", emu->gpu_regs.val[n]);
}
}
static void
dump_pipe_register(struct emu *emu, unsigned n)
{
printf(" PIPE: ");
print_pipe_reg(n);
printf(": ");
if (BITSET_TEST(emu->pipe_regs.written, n)) {
printdelta("%08x\n", emu->pipe_regs.val[n]);
} else {
printf("%08x\n", emu->pipe_regs.val[n]);
}
}
static void
dump_control_register(struct emu *emu, unsigned n)
{
printf(" CTRL: ");
print_control_reg(n);
printf(": ");
if (BITSET_TEST(emu->control_regs.written, n)) {
printdelta("%08x\n", emu->control_regs.val[n]);
} else {
printf("%08x\n", emu->control_regs.val[n]);
}
}
static void
dump_gpumem(struct emu *emu, uintptr_t addr)
{
uint32_t val = emu_mem_read_dword(emu, addr);
printf(" MEM: 0x%016"PRIx64": ", addr);
if (addr == emu->gpumem_written) {
printdelta("0x%08x\n", val);
} else {
printf("0x%08x\n", val);
}
}
static void
emu_write_gpr_prompt(struct emu *emu)
{
clear_line();
printf(" GPR register (name or offset) and value: ");
const char *name;
const char *value;
if (read_two_values(&name, &value))
return;
unsigned offset = afuc_gpr_reg(name);
uint32_t val = strtoul(value, NULL, 0);
emu_set_gpr_reg(emu, offset, val);
}
static void
emu_write_control_prompt(struct emu *emu)
{
clear_line();
printf(" Control register (name or offset) and value: ");
const char *name;
const char *value;
if (read_two_values(&name, &value))
return;
unsigned offset = afuc_control_reg(name);
uint32_t val = strtoul(value, NULL, 0);
emu_set_control_reg(emu, offset, val);
}
static void
emu_dump_control_prompt(struct emu *emu)
{
clear_line();
printf(" Control register (name or offset): ");
const char *name;
if (read_one_value(&name))
return;
printf("\n");
unsigned offset = afuc_control_reg(name);
dump_control_register(emu, offset);
}
static void
emu_write_gpu_prompt(struct emu *emu)
{
clear_line();
printf(" GPU register (name or offset) and value: ");
const char *name;
const char *value;
if (read_two_values(&name, &value))
return;
unsigned offset = afuc_gpu_reg(name);
uint32_t val = strtoul(value, NULL, 0);
emu_set_gpu_reg(emu, offset, val);
}
static void
emu_dump_gpu_prompt(struct emu *emu)
{
clear_line();
printf(" GPU register (name or offset): ");
const char *name;
if (read_one_value(&name))
return;
printf("\n");
unsigned offset = afuc_gpu_reg(name);
dump_gpu_register(emu, offset);
}
static void
emu_write_mem_prompt(struct emu *emu)
{
clear_line();
printf(" GPU memory offset and value: ");
const char *offset;
const char *value;
if (read_two_values(&offset, &value))
return;
uintptr_t addr = strtoull(offset, NULL, 0);
uint32_t val = strtoul(value, NULL, 0);
emu_mem_write_dword(emu, addr, val);
}
static void
emu_dump_mem_prompt(struct emu *emu)
{
clear_line();
printf(" GPU memory offset: ");
const char *offset;
if (read_one_value(&offset))
return;
printf("\n");
uintptr_t addr = strtoull(offset, NULL, 0);
dump_gpumem(emu, addr);
}
static void
emu_dump_prompt(struct emu *emu)
{
do {
clear_line();
printf(" dump: GPR (r)egisters, (c)ontrol register, (g)pu register, (m)emory: ");
int c = readchar();
printf("%c\n", c);
if (c == 'r') {
/* Since there aren't too many GPR registers, just dump
* them all:
*/
dump_gpr_registers(emu);
break;
} else if (c == 'c') {
emu_dump_control_prompt(emu);
break;
} else if (c == 'g') {
emu_dump_gpu_prompt(emu);
break;
} else if (c == 'm') {
emu_dump_mem_prompt(emu);
break;
} else {
printf("invalid option: '%c'\n", c);
break;
}
} while (true);
}
static void
emu_write_prompt(struct emu *emu)
{
do {
clear_line();
printf(" write: GPR (r)egister, (c)ontrol register, (g)pu register, (m)emory: ");
int c = readchar();
printf("%c\n", c);
if (c == 'r') {
emu_write_gpr_prompt(emu);
break;
} else if (c == 'c') {
emu_write_control_prompt(emu);
break;
} else if (c == 'g') {
emu_write_gpu_prompt(emu);
break;
} else if (c == 'm') {
emu_write_mem_prompt(emu);
break;
} else {
printf("invalid option: '%c'\n", c);
break;
}
} while (true);
}
static void
emu_packet_prompt(struct emu *emu)
{
clear_line();
printf(" Enter packet (opc or register name), followed by payload: ");
fflush(stdout);
char *p;
if (readline(&p) < 0)
return;
printf("\n");
const char *name = extract_string(&p);
/* Read the payload, so we can know the size to generate correct header: */
uint32_t payload[0x7f];
unsigned cnt = 0;
do {
const char *val = extract_string(&p);
if (!val)
break;
assert(cnt < ARRAY_SIZE(payload));
payload[cnt++] = strtoul(val, NULL, 0);
} while (true);
uint32_t hdr;
if (afuc_pm4_id(name) >= 0) {
unsigned opcode = afuc_pm4_id(name);
hdr = pm4_pkt7_hdr(opcode, cnt);
} else {
unsigned regindx = afuc_gpu_reg(name);
hdr = pm4_pkt4_hdr(regindx, cnt);
}
ASSERTED bool ret = emu_queue_push(&emu->roq, hdr);
assert(ret);
for (unsigned i = 0; i < cnt; i++) {
ASSERTED bool ret = emu_queue_push(&emu->roq, payload[i]);
assert(ret);
}
}
void
emu_main_prompt(struct emu *emu)
{
if (emu->run_mode)
return;
do {
clear_line();
printf("(s)tep, (r)un, (d)ump, (w)rite, (p)acket, (h)elp, (q)uit: ");
int c = readchar();
printf("%c\n", c);
if (c == 's') {
break;
} else if (c == 'r') {
emu->run_mode = true;
break;
} else if (c == 'd') {
emu_dump_prompt(emu);
} else if (c == 'w') {
emu_write_prompt(emu);
} else if (c == 'p') {
emu_packet_prompt(emu);
} else if (c == 'h') {
printf(" (s)tep - single step to next instruction\n");
printf(" (r)un - run until next waitin\n");
printf(" (d)ump - dump memory/register menu\n");
printf(" (w)rite - write memory/register menu\n");
printf(" (p)acket - inject a pm4 packet\n");
printf(" (h)elp - show this usage message\n");
printf(" (q)uit - exit emulator\n");
} else if (c == 'q') {
printf("\n");
exit(0);
} else {
printf("invalid option: '%c'\n", c);
}
} while (true);
}
void
emu_clear_state_change(struct emu *emu)
{
memset(emu->control_regs.written, 0, sizeof(emu->control_regs.written));
memset(emu->pipe_regs.written, 0, sizeof(emu->pipe_regs.written));
memset(emu->gpu_regs.written, 0, sizeof(emu->gpu_regs.written));
memset(emu->gpr_regs.written, 0, sizeof(emu->gpr_regs.written));
emu->gpumem_written = ~0;
}
void
emu_dump_state_change(struct emu *emu)
{
unsigned i;
/* Print the GPRs that changed: */
BITSET_FOREACH_SET (i, emu->gpr_regs.written, EMU_NUM_GPR_REGS) {
dump_gpr_register(emu, i);
}
BITSET_FOREACH_SET (i, emu->gpu_regs.written, EMU_NUM_GPU_REGS) {
dump_gpu_register(emu, i);
}
BITSET_FOREACH_SET (i, emu->pipe_regs.written, EMU_NUM_PIPE_REGS) {
dump_pipe_register(emu, i);
}
BITSET_FOREACH_SET (i, emu->control_regs.written, EMU_NUM_CONTROL_REGS) {
dump_control_register(emu, i);
}
if (emu->gpumem_written != ~0) {
dump_gpumem(emu, emu->gpumem_written);
}
}

444
src/freedreno/afuc/emu.c Normal file
View File

@ -0,0 +1,444 @@
/*
* 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include "util/u_math.h"
#include "freedreno_pm4.h"
#include "emu.h"
#include "util.h"
#define rotl32(x,r) (((x) << (r)) | ((x) >> (32 - (r))))
#define rotl64(x,r) (((x) << (r)) | ((x) >> (64 - (r))))
/**
* AFUC emulator. Currently only supports a6xx
*
* TODO to add a5xx it might be easier to compile this multiple times
* with conditional compile to deal with differences between generations.
*/
static uint32_t
emu_alu(struct emu *emu, afuc_opc opc, uint32_t src1, uint32_t src2)
{
uint64_t tmp;
switch (opc) {
case OPC_ADD:
tmp = (uint64_t)src1 + (uint64_t)src2;
emu->carry = tmp >> 32;
return (uint32_t)tmp;
case OPC_ADDHI:
return src1 + src2 + emu->carry;
case OPC_SUB:
tmp = (uint64_t)src1 - (uint64_t)src2;
emu->carry = tmp >> 32;
return (uint32_t)tmp;
case OPC_SUBHI:
return src1 - src2 + emu->carry;
case OPC_AND:
return src1 & src2;
case OPC_OR:
return src1 | src2;
case OPC_XOR:
return src1 ^ src2;
case OPC_NOT:
return ~src1;
case OPC_SHL:
return src1 << src2;
case OPC_USHR:
return src1 >> src2;
case OPC_ISHR:
return (int32_t)src1 >> src2;
case OPC_ROT:
if (src2 & 0x80000000)
return rotl64(src1, -*(int32_t *)&src2);
else
return rotl32(src1, src2);
case OPC_MUL8:
return (src1 & 0xff) * (src2 & 0xff);
case OPC_MIN:
return MIN2(src1, src2);
case OPC_MAX:
return MAX2(src1, src2);
case OPC_CMP:
if (src1 > src2)
return 0x00;
else if (src1 == src2)
return 0x2b;
return 0x1e;
case OPC_MSB:
if (!src2)
return 0;
return util_last_bit(src2) - 1;
default:
printf("unhandled alu opc: 0x%02x\n", opc);
exit(1);
}
}
/**
* Helper to calculate load/store address based on LOAD_STORE_HI
*/
static uintptr_t
load_store_addr(struct emu *emu, unsigned gpr)
{
EMU_CONTROL_REG(LOAD_STORE_HI);
uintptr_t addr = emu_get_reg32(emu, &LOAD_STORE_HI);
addr <<= 32;
return addr + emu_get_gpr_reg(emu, gpr);
}
static void
emu_instr(struct emu *emu, afuc_instr *instr)
{
uint32_t rem = emu_get_gpr_reg(emu, REG_REM);
afuc_opc opc;
bool rep;
afuc_get_opc(instr, &opc, &rep);
switch (opc) {
case OPC_NOP:
break;
case OPC_ADD ... OPC_CMP: {
uint32_t val = emu_alu(emu, opc,
emu_get_gpr_reg(emu, instr->alui.src),
instr->alui.uimm);
emu_set_gpr_reg(emu, instr->alui.dst, val);
break;
}
case OPC_MOVI: {
uint32_t val = instr->movi.uimm << instr->movi.shift;
emu_set_gpr_reg(emu, instr->movi.dst, val);
break;
}
case OPC_ALU: {
uint32_t val = emu_alu(emu, instr->alu.alu,
emu_get_gpr_reg(emu, instr->alu.src1),
emu_get_gpr_reg(emu, instr->alu.src2));
emu_set_gpr_reg(emu, instr->alu.dst, val);
if (instr->alu.xmov) {
unsigned m = MIN2(instr->alu.xmov, rem);
assert(m <= 3);
if (m == 1) {
emu_set_gpr_reg(emu, REG_REM, --rem);
emu_dump_state_change(emu);
emu_set_gpr_reg(emu, REG_DATA,
emu_get_gpr_reg(emu, instr->alu.src2));
} else if (m == 2) {
emu_set_gpr_reg(emu, REG_REM, --rem);
emu_dump_state_change(emu);
emu_set_gpr_reg(emu, REG_DATA,
emu_get_gpr_reg(emu, instr->alu.src2));
emu_set_gpr_reg(emu, REG_REM, --rem);
emu_dump_state_change(emu);
emu_set_gpr_reg(emu, REG_DATA,
emu_get_gpr_reg(emu, instr->alu.src2));
} else if (m == 3) {
emu_set_gpr_reg(emu, REG_REM, --rem);
emu_dump_state_change(emu);
emu_set_gpr_reg(emu, REG_DATA,
emu_get_gpr_reg(emu, instr->alu.src2));
emu_set_gpr_reg(emu, REG_REM, --rem);
emu_dump_state_change(emu);
emu_set_gpr_reg(emu, instr->alu.dst,
emu_get_gpr_reg(emu, instr->alu.src2));
emu_set_gpr_reg(emu, REG_REM, --rem);
emu_dump_state_change(emu);
emu_set_gpr_reg(emu, REG_DATA,
emu_get_gpr_reg(emu, instr->alu.src2));
}
}
break;
}
case OPC_CWRITE6: {
uint32_t src1 = emu_get_gpr_reg(emu, instr->control.src1);
uint32_t src2 = emu_get_gpr_reg(emu, instr->control.src2);
if (instr->control.flags == 0x4) {
emu_set_gpr_reg(emu, instr->control.src2, src2 + instr->control.uimm);
} else if (instr->control.flags) {
printf("unhandled flags: %x\n", instr->control.flags);
}
emu_set_control_reg(emu, src2 + instr->control.uimm, src1);
break;
}
case OPC_CREAD6: {
uint32_t src2 = emu_get_gpr_reg(emu, instr->control.src2);
if (instr->control.flags == 0x4) {
emu_set_gpr_reg(emu, instr->control.src2, src2 + instr->control.uimm);
} else if (instr->control.flags) {
printf("unhandled flags: %x\n", instr->control.flags);
}
emu_set_gpr_reg(emu, instr->control.src1,
emu_get_control_reg(emu, src2 + instr->control.uimm));
break;
}
case OPC_LOAD6: {
uintptr_t addr = load_store_addr(emu, instr->control.src2) +
instr->control.uimm;
if (instr->control.flags == 0x4) {
uint32_t src2 = emu_get_gpr_reg(emu, instr->control.src2);
emu_set_gpr_reg(emu, instr->control.src2, src2 + instr->control.uimm);
} else if (instr->control.flags) {
printf("unhandled flags: %x\n", instr->control.flags);
}
uint32_t val = emu_mem_read_dword(emu, addr);
emu_set_gpr_reg(emu, instr->control.src1, val);
break;
}
case OPC_STORE6: {
uintptr_t addr = load_store_addr(emu, instr->control.src2) +
instr->control.uimm;
if (instr->control.flags == 0x4) {
uint32_t src2 = emu_get_gpr_reg(emu, instr->control.src2);
emu_set_gpr_reg(emu, instr->control.src2, src2 + instr->control.uimm);
} else if (instr->control.flags) {
printf("unhandled flags: %x\n", instr->control.flags);
}
uint32_t val = emu_get_gpr_reg(emu, instr->control.src1);
emu_mem_write_dword(emu, addr, val);
break;
}
case OPC_BRNEI ... OPC_BREQB: {
uint32_t off = emu->gpr_regs.pc + instr->br.ioff;
uint32_t src = emu_get_gpr_reg(emu, instr->br.src);
if (opc == OPC_BRNEI) {
if (src != instr->br.bit_or_imm)
emu->branch_target = off;
} else if (opc == OPC_BREQI) {
if (src == instr->br.bit_or_imm)
emu->branch_target = off;
} else if (opc == OPC_BRNEB) {
if (!(src & (1 << instr->br.bit_or_imm)))
emu->branch_target = off;
} else if (opc == OPC_BREQB) {
if (src & (1 << instr->br.bit_or_imm))
emu->branch_target = off;
} else {
assert(0);
}
break;
}
case OPC_RET: {
assert(emu->call_stack_idx > 0);
/* counter-part to 'call' instruction, also has a delay slot: */
emu->branch_target = emu->call_stack[--emu->call_stack_idx];
break;
}
case OPC_CALL: {
assert(emu->call_stack_idx < ARRAY_SIZE(emu->call_stack));
/* call looks to have same delay-slot behavior as branch/etc, so
* presumably the return PC is two instructions later:
*/
emu->call_stack[emu->call_stack_idx++] = emu->gpr_regs.pc + 2;
emu->branch_target = instr->call.uoff;
break;
}
case OPC_WIN: {
assert(!emu->branch_target);
emu->run_mode = false;
emu->waitin = true;
break;
}
/* OPC_PREEMPTLEAVE6 */
case OPC_SETSECURE: {
// TODO this acts like a conditional branch, but in which case
// does it branch?
break;
}
default:
printf("unhandled opc: 0x%02x\n", opc);
exit(1);
}
if (rep) {
assert(rem > 0);
emu_set_gpr_reg(emu, REG_REM, --rem);
}
}
void
emu_step(struct emu *emu)
{
afuc_instr *instr = (void *)&emu->instrs[emu->gpr_regs.pc];
afuc_opc opc;
bool rep;
emu_main_prompt(emu);
uint32_t branch_target = emu->branch_target;
emu->branch_target = 0;
bool waitin = emu->waitin;
emu->waitin = false;
afuc_get_opc(instr, &opc, &rep);
if (rep) {
do {
if (!emu_get_gpr_reg(emu, REG_REM))
break;
emu_clear_state_change(emu);
emu_instr(emu, instr);
/* defer last state-change dump until after any
* post-delay-slot handling below:
*/
if (emu_get_gpr_reg(emu, REG_REM))
emu_dump_state_change(emu);
} while (true);
} else {
emu_clear_state_change(emu);
emu_instr(emu, instr);
}
emu->gpr_regs.pc++;
if (branch_target) {
emu->gpr_regs.pc = branch_target;
}
if (waitin) {
uint32_t hdr = emu_get_gpr_reg(emu, 1);
uint32_t id, count;
if (pkt_is_type4(hdr)) {
id = afuc_pm4_id("PKT4");
count = type4_pkt_size(hdr);
/* Possibly a hack, not sure what the hw actually
* does here, but we want to mask out the pkt
* type field from the hdr, so that PKT4 handler
* doesn't see it and interpret it as part as the
* register offset:
*/
emu->gpr_regs.val[1] &= 0x0fffffff;
} else if (pkt_is_type7(hdr)) {
id = cp_type7_opcode(hdr);
count = type7_pkt_size(hdr);
} else {
printf("Invalid opcode: 0x%08x\n", hdr);
exit(1); /* GPU goes *boom* */
}
assert(id < ARRAY_SIZE(emu->jmptbl));
emu_set_gpr_reg(emu, REG_REM, count);
emu->gpr_regs.pc = emu->jmptbl[id];
}
emu_dump_state_change(emu);
}
static void
check_access(struct emu *emu, uintptr_t gpuaddr, unsigned sz)
{
if ((gpuaddr % sz) != 0) {
printf("unaligned access fault: %p\n", (void *)gpuaddr);
exit(1);
}
if ((gpuaddr + sz) >= EMU_MEMORY_SIZE) {
printf("iova fault: %p\n", (void *)gpuaddr);
exit(1);
}
}
uint32_t
emu_mem_read_dword(struct emu *emu, uintptr_t gpuaddr)
{
check_access(emu, gpuaddr, 4);
return *(uint32_t *)(emu->gpumem + gpuaddr);
}
static void
mem_write_dword(struct emu *emu, uintptr_t gpuaddr, uint32_t val)
{
check_access(emu, gpuaddr, 4);
*(uint32_t *)(emu->gpumem + gpuaddr) = val;
}
void
emu_mem_write_dword(struct emu *emu, uintptr_t gpuaddr, uint32_t val)
{
mem_write_dword(emu, gpuaddr, val);
assert(emu->gpumem_written == ~0);
emu->gpumem_written = gpuaddr;
}
void
emu_init(struct emu *emu)
{
emu->gpumem = mmap(NULL, EMU_MEMORY_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE,
0, 0);
if (emu->gpumem == MAP_FAILED) {
printf("Could not allocate GPU memory: %s\n", strerror(errno));
exit(1);
}
/* Copy the instructions into GPU memory: */
for (unsigned i = 0; i < emu->sizedwords; i++) {
mem_write_dword(emu, EMU_INSTR_BASE + (4 * i), emu->instrs[i]);
}
printf("instruction base: %p\n", (void *)(uintptr_t)EMU_INSTR_BASE);
/* Setup the address of the SQE fw, just use the normal CPU ptr address: */
EMU_GPU_REG(CP_SQE_INSTR_BASE);
emu_set_reg64(emu, &CP_SQE_INSTR_BASE, EMU_INSTR_BASE);
}

277
src/freedreno/afuc/emu.h Normal file
View File

@ -0,0 +1,277 @@
/*
* 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.
*/
#ifndef _EMU_H_
#define _EMU_H_
#include <stdbool.h>
#include <stdint.h>
#include "util/bitset.h"
#include "afuc.h"
#define EMU_NUM_GPR_REGS 32
struct emu_gpr_regs {
BITSET_DECLARE(written, EMU_NUM_GPR_REGS);
union {
uint32_t pc;
uint32_t val[EMU_NUM_GPR_REGS];
};
};
#define EMU_NUM_CONTROL_REGS 0x1000
struct emu_control_regs {
BITSET_DECLARE(written, EMU_NUM_CONTROL_REGS);
uint32_t val[EMU_NUM_CONTROL_REGS];
};
#define EMU_NUM_GPU_REGS 0x10000
struct emu_gpu_regs {
BITSET_DECLARE(written, EMU_NUM_GPU_REGS);
uint32_t val[EMU_NUM_GPU_REGS];
};
#define EMU_NUM_PIPE_REGS 0x100
struct emu_pipe_regs {
BITSET_DECLARE(written, EMU_NUM_PIPE_REGS);
uint32_t val[EMU_NUM_PIPE_REGS];
};
/**
* A simple queue implementation to buffer up cmdstream for the
* emulated firmware to consume
*/
struct emu_queue {
unsigned head, tail, count;
uint32_t fifo[0x100];
};
static inline bool
emu_queue_push(struct emu_queue *q, uint32_t val)
{
if (q->count >= ARRAY_SIZE(q->fifo))
return false;
q->count++;
q->head++;
q->head %= ARRAY_SIZE(q->fifo);
q->fifo[q->head] = val;
return true;
}
static inline bool
emu_queue_pop(struct emu_queue *q, uint32_t *val)
{
if (!q->count)
return false;
q->count--;
q->tail++;
q->tail %= ARRAY_SIZE(q->fifo);
*val = q->fifo[q->tail];
return true;
}
/**
* Draw-state (ie. CP_SET_DRAW_STATE) related emulation
*/
struct emu_draw_state {
unsigned prev_draw_state_sel;
unsigned write_idx;
struct {
union {
uint32_t hdr;
struct {
uint16_t count; /* # of dwords */
uint16_t mode_mask;
};
};
union {
uint32_t base_lohi[2];
uint64_t base;
};
uint64_t sds_base;
uint32_t sds_dwords;
} state[32];
};
/**
* The GPU memory size:
*
* The size is a bit arbitrary, and could be increased. The backing
* storage is a MAP_ANONYMOUS mapping so untouched pages should not
* have a cost other than consuming virtual address space.
*
* Use something >4gb so we can test that anything doing GPU pointer
* math correctly handles rollover
*/
#define EMU_MEMORY_SIZE 0x200000000
/**
* The GPU "address" of the instructions themselves:
*
* Note address is kind of arbitrary, but should be something non-
* zero to sanity check the bootstrap process and packet-table
* loading
*/
#define EMU_INSTR_BASE 0x1000
/**
* Emulated hw state.
*/
struct emu {
uint32_t *instrs;
unsigned sizedwords;
struct emu_control_regs control_regs;
struct emu_pipe_regs pipe_regs;
struct emu_gpu_regs gpu_regs;
struct emu_gpr_regs gpr_regs;
struct emu_draw_state draw_state;
/* branch target to jump to after next instruction (ie. after delay-
* slot):
*/
uint32_t branch_target;
/* executed waitin, jump to handler after next instruction (ie. after
* delay-slot):
*/
bool waitin;
/* (r)un mode, don't stop for input until next waitin: */
bool run_mode;
/* carry-bits for add/sub for addhi/subhi */
uint32_t carry;
/* call-stack of saved PCs.. I expect this to be a fixed size, but not
* sure what the actual size is
*/
uint32_t call_stack[5];
int call_stack_idx;
/* packet table (aka jmptable) has offsets for pm4 packet handlers: */
uint32_t jmptbl[0x80];
/* In reality ROQ is actually multiple queues, but we don't try
* to model the hw that exactly (but instead only model the behavior)
* so we just use this to buffer up cmdstream input
*/
struct emu_queue roq;
/* Mode for writes to $data: */
enum {
DATA_ADDR,
DATA_USRADDR,
DATA_PIPE,
} data_mode;
/* GPU address space: */
void *gpumem;
/* A bitset would be prohibitively large to track memory writes, to
* show in the state-change dump. But we can only write a single
* dword per instruction (given that for (rep) and/or (xmov) we
* dump state change at each "step" of the instruction.
*
* ~0 means no memory write
*/
uintptr_t gpumem_written;
};
/*
* API for disasm to use:
*/
void emu_step(struct emu *emu);
void emu_init(struct emu *emu);
/*
* Internal APIs
*/
uint32_t emu_mem_read_dword(struct emu *emu, uintptr_t gpuaddr);
void emu_mem_write_dword(struct emu *emu, uintptr_t gpuaddr, uint32_t val);
/* UI: */
void emu_main_prompt(struct emu *emu);
void emu_clear_state_change(struct emu *emu);
void emu_dump_state_change(struct emu *emu);
/* Registers: */
uint32_t emu_get_gpr_reg(struct emu *emu, unsigned n);
void emu_set_gpr_reg(struct emu *emu, unsigned n, uint32_t val);
void emu_set_gpu_reg(struct emu *emu, unsigned n, uint32_t val);
uint32_t emu_get_control_reg(struct emu *emu, unsigned n);
void emu_set_control_reg(struct emu *emu, unsigned n, uint32_t val);
/* Register helpers for fixed fxn emulation, to avoid lots of boilerplate
* for accessing other pipe/control registers.
*
* Example:
* EMU_CONTROL_REG(REG_NAME);
* val = emu_get_reg32(emu, &SOME_REG);
*/
struct emu_reg_accessor;
struct emu_reg {
const char *name;
const struct emu_reg_accessor *accessor;
unsigned offset;
};
extern const struct emu_reg_accessor emu_control_accessor;
extern const struct emu_reg_accessor emu_pipe_accessor;
extern const struct emu_reg_accessor emu_gpu_accessor;
#define EMU_CONTROL_REG(name) static struct emu_reg name = { #name, &emu_control_accessor, ~0 }
#define EMU_PIPE_REG(name) static struct emu_reg name = { #name, &emu_pipe_accessor, ~0 }
#define EMU_GPU_REG(name) static struct emu_reg name = { #name, &emu_gpu_accessor, ~0 }
unsigned emu_reg_offset(struct emu_reg *reg);
uint32_t emu_get_reg32(struct emu *emu, struct emu_reg *reg);
uint64_t emu_get_reg64(struct emu *emu, struct emu_reg *reg);
void emu_set_reg32(struct emu *emu, struct emu_reg *reg, uint32_t val);
void emu_set_reg64(struct emu *emu, struct emu_reg *reg, uint64_t val);
/* Draw-state control reg emulation: */
uint32_t emu_get_draw_state_reg(struct emu *emu, unsigned n);
void emu_set_draw_state_reg(struct emu *emu, unsigned n, uint32_t val);
/* Helpers: */
#define printdelta(fmt, ...) afuc_printc(AFUC_ERR, fmt, ##__VA_ARGS__)
#endif /* _ASM_H_ */

View File

@ -60,6 +60,11 @@ disasm = executable(
'afuc-disasm',
[
'disasm.c',
'emu.c',
'emu.h',
'emu-ds.c',
'emu-regs.c',
'emu-ui.c',
'util.c',
'util.h',
],
@ -73,7 +78,8 @@ disasm = executable(
link_with: [
libfreedreno_rnn,
],
dependencies: [],
dependencies: [
],
build_by_default : with_tools.contains('freedreno'),
install: install_fd_decode_tools,
)

View File

@ -29,6 +29,7 @@
#include "rnn.h"
#include "rnndec.h"
#include "afuc.h"
#include "util.h"
static struct rnndeccontext *ctx;
@ -103,6 +104,24 @@ afuc_pipe_reg(const char *name)
return reg(pipe_regs, "pipe", name);
}
/**
* "void" pipe regs don't have a value written, the $addr right is
* enough to trigger what they do
*/
bool
afuc_pipe_reg_is_void(unsigned id)
{
if (rnndec_checkaddr(ctx, pipe_regs, id, 0)) {
struct rnndecaddrinfo *info = rnndec_decodeaddr(ctx, pipe_regs, id, 0);
free(info->name);
bool ret = !strcmp(info->typeinfo->name, "void");
free(info);
return ret;
} else {
return false;
}
}
/**
* Map offset to pipe reg name (or NULL), caller frees
*/
@ -158,6 +177,37 @@ afuc_gpu_reg_name(unsigned id)
return NULL;
}
unsigned
afuc_gpr_reg(const char *name)
{
/* If it starts with '$' just swallow it: */
if (name[0] == '$')
name++;
/* handle aliases: */
if (!strcmp(name, "rem")) {
return REG_REM;
} else if (!strcmp(name, "memdata")) {
return REG_MEMDATA;
} else if (!strcmp(name, "addr")) {
return REG_ADDR;
} else if (!strcmp(name, "regdata")) {
return REG_REGDATA;
} else if (!strcmp(name, "usraddr")) {
return REG_USRADDR;
} else if (!strcmp(name, "data")) {
return REG_DATA;
} else {
char *endptr = NULL;
unsigned val = strtol(name, &endptr, 16);
if (endptr && *endptr) {
printf("invalid gpr reg: %s\n", name);
exit(2);
}
return val;
}
}
static int
find_enum_val(struct rnnenum *en, const char *name)
{

View File

@ -35,10 +35,13 @@ char * afuc_control_reg_name(unsigned id);
unsigned afuc_pipe_reg(const char *name);
char * afuc_pipe_reg_name(unsigned id);
bool afuc_pipe_reg_is_void(unsigned id);
unsigned afuc_gpu_reg(const char *name);
char * afuc_gpu_reg_name(unsigned id);
unsigned afuc_gpr_reg(const char *name);
int afuc_pm4_id(const char *name);
const char * afuc_pm_id_name(unsigned id);