summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/freedreno/afuc/afuc.h17
-rw-r--r--src/freedreno/afuc/disasm.c44
-rw-r--r--src/freedreno/afuc/emu-ds.c91
-rw-r--r--src/freedreno/afuc/emu-regs.c371
-rw-r--r--src/freedreno/afuc/emu-ui.c531
-rw-r--r--src/freedreno/afuc/emu.c444
-rw-r--r--src/freedreno/afuc/emu.h277
-rw-r--r--src/freedreno/afuc/meson.build8
-rw-r--r--src/freedreno/afuc/util.c50
-rw-r--r--src/freedreno/afuc/util.h3
10 files changed, 1829 insertions, 7 deletions
diff --git a/src/freedreno/afuc/afuc.h b/src/freedreno/afuc/afuc.h
index 8ec850644e4..059b24cef3e 100644
--- a/src/freedreno/afuc/afuc.h
+++ b/src/freedreno/afuc/afuc.h
@@ -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_ */
diff --git a/src/freedreno/afuc/disasm.c b/src/freedreno/afuc/disasm.c
index 145fdbda9a7..44e7eaf1017 100644
--- a/src/freedreno/afuc/disasm.c
+++ b/src/freedreno/afuc/disasm.c
@@ -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();
diff --git a/src/freedreno/afuc/emu-ds.c b/src/freedreno/afuc/emu-ds.c
new file mode 100644
index 00000000000..39ccf1da3c5
--- /dev/null
+++ b/src/freedreno/afuc/emu-ds.c
@@ -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;
+ }
+}
diff --git a/src/freedreno/afuc/emu-regs.c b/src/freedreno/afuc/emu-regs.c
new file mode 100644
index 00000000000..91802253eac
--- /dev/null
+++ b/src/freedreno/afuc/emu-regs.c
@@ -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);
+}
diff --git a/src/freedreno/afuc/emu-ui.c b/src/freedreno/afuc/emu-ui.c
new file mode 100644
index 00000000000..0b24ea5f3aa
--- /dev/null
+++ b/src/freedreno/afuc/emu-ui.c
@@ -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);
+ }
+}
diff --git a/src/freedreno/afuc/emu.c b/src/freedreno/afuc/emu.c
new file mode 100644
index 00000000000..f7296352848
--- /dev/null
+++ b/src/freedreno/afuc/emu.c
@@ -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);
+}
+
diff --git a/src/freedreno/afuc/emu.h b/src/freedreno/afuc/emu.h
new file mode 100644
index 00000000000..d8bc4f0b98e
--- /dev/null
+++ b/src/freedreno/afuc/emu.h
@@ -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_ */
diff --git a/src/freedreno/afuc/meson.build b/src/freedreno/afuc/meson.build
index 43c6153fde8..878d4e74d8b 100644
--- a/src/freedreno/afuc/meson.build
+++ b/src/freedreno/afuc/meson.build
@@ -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,
)
diff --git a/src/freedreno/afuc/util.c b/src/freedreno/afuc/util.c
index c0be0926816..b19c21c974f 100644
--- a/src/freedreno/afuc/util.c
+++ b/src/freedreno/afuc/util.c
@@ -29,6 +29,7 @@
#include "rnn.h"
#include "rnndec.h"
+#include "afuc.h"
#include "util.h"
static struct rnndeccontext *ctx;
@@ -104,6 +105,24 @@ afuc_pipe_reg(const char *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
*/
char *
@@ -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)
{
diff --git a/src/freedreno/afuc/util.h b/src/freedreno/afuc/util.h
index 4b70d221ae4..e63de5e2769 100644
--- a/src/freedreno/afuc/util.h
+++ b/src/freedreno/afuc/util.h
@@ -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);