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 + #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 +#include +#include +#include + +#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 +#include +#include +#include + +#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(®_WRITE)) { + uint32_t write_addr = emu_get_reg32(emu, ®_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, ®_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, ®_READ_DWORDS); + unsigned read_addr = emu_get_reg32(emu, ®_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, ®_READ_DWORDS, read_dwords - 1); + emu_set_reg32(emu, ®_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 +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include + +#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 +#include + +#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; @@ -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) { 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);