From 542ae9de0198e1677acda91b3d690c336f611e17 Mon Sep 17 00:00:00 2001 From: Connor Abbott Date: Mon, 18 Dec 2023 14:02:53 -0500 Subject: [PATCH] freedreno/afuc: Improve jump table handling Some a7xx firmwares have junk after the LPAC jump table, and the old method of hardcoding the location of the jumptable and the jumptable offset when assembling and disassembling also doesn't work well on a7xx where the location of the jumptable offset changes. Make jump tables explicit in the disassembly with a ".jumptbl" directive which expands to contain the contents of the jump table, make disassembly find the jump table and emit the directive there. Then add the ability for a literal to reference a label, which will be used for the jump table offset at the beginning of the firmware. The disassembler guesses when a word is actually the jump table offset and replaces it with a relocation. This restores the ability to disassemble a630_sqe.fw and a650_sqe.fw and reassemble to an identical binary without modifying the disassembly to remove the jump table. Part-of: --- .../.gitlab-ci/reference/afuc_test.asm | 132 +----------------- src/freedreno/.gitlab-ci/traces/afuc_test.asm | 5 +- src/freedreno/afuc/afuc.h | 11 ++ src/freedreno/afuc/asm.c | 76 +++++----- src/freedreno/afuc/asm.h | 1 + src/freedreno/afuc/disasm.c | 75 +++++++++- src/freedreno/afuc/lexer.l | 2 + src/freedreno/afuc/parser.y | 8 +- 8 files changed, 139 insertions(+), 171 deletions(-) diff --git a/src/freedreno/.gitlab-ci/reference/afuc_test.asm b/src/freedreno/.gitlab-ci/reference/afuc_test.asm index 198b84331b8..09ec1b0f875 100644 --- a/src/freedreno/.gitlab-ci/reference/afuc_test.asm +++ b/src/freedreno/.gitlab-ci/reference/afuc_test.asm @@ -2,7 +2,7 @@ ; Version: 01000001 [01000001] -[01000081] +[#jumptbl] mov $01, 0x830 ; CP_SQE_INSTR_BASE mov $02, 0x2 cwrite $01, [$00 + @REG_READ_ADDR] @@ -279,131 +279,5 @@ UNKN96: UNKN97: waitin mov $01, $data -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[00000074] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[00000048] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[00000033] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[00000025] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[00000030] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[00000022] -[0000007f] -[0000007f] -[0000007f] -[0000002c] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[00000039] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000006b] -[0000007f] -[0000005e] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] -[0000007f] +jumptbl: +.jumptbl diff --git a/src/freedreno/.gitlab-ci/traces/afuc_test.asm b/src/freedreno/.gitlab-ci/traces/afuc_test.asm index e8d6f8b2b42..48203046b51 100644 --- a/src/freedreno/.gitlab-ci/traces/afuc_test.asm +++ b/src/freedreno/.gitlab-ci/traces/afuc_test.asm @@ -27,7 +27,7 @@ ; this to avoid having to host the actual firmware, especially the disassembled ; version, in Mesa. [01000001] -[01000000] +[#jumptbl] loc02: ; packet table loading: mov $01, 0x0830 ; CP_SQE_INSTR_BASE @@ -334,3 +334,6 @@ UNKN126: UNKN127: waitin mov $01, $data + +jumptbl: +.jumptbl diff --git a/src/freedreno/afuc/afuc.h b/src/freedreno/afuc/afuc.h index 4722526e33b..2791c72c6f0 100644 --- a/src/freedreno/afuc/afuc.h +++ b/src/freedreno/afuc/afuc.h @@ -126,6 +126,7 @@ typedef enum { OPC_BRNE, OPC_JUMP, OPC_RAW_LITERAL, + OPC_JUMPTBL, } afuc_opc; /** @@ -181,6 +182,16 @@ struct afuc_instr { bool peek : 1; }; +/* Literal offsets are sometimes encoded as NOP instructions, which on a6xx+ + * must have a high 8 bits of 0x01. + */ +static inline uint32_t +afuc_nop_literal(uint32_t x, unsigned gpuver) +{ + assert((x >> 24) == 0); + return gpuver < 6 ? x : x | (1 << 24); +} + void print_control_reg(uint32_t id); void print_sqe_reg(uint32_t id); void print_pipe_reg(uint32_t id); diff --git a/src/freedreno/afuc/asm.c b/src/freedreno/afuc/asm.c index e426905f49e..8c86fce061c 100644 --- a/src/freedreno/afuc/asm.c +++ b/src/freedreno/afuc/asm.c @@ -88,6 +88,8 @@ int gpuver; static struct afuc_instr instructions[0x4000]; static unsigned num_instructions; +static unsigned instr_offset; + static struct asm_label labels[0x512]; static unsigned num_labels; @@ -96,6 +98,7 @@ next_instr(afuc_opc opc) { struct afuc_instr *ai = &instructions[num_instructions++]; assert(num_instructions < ARRAY_SIZE(instructions)); + instr_offset++; ai->opc = opc; return ai; } @@ -107,10 +110,19 @@ decl_label(const char *str) assert(num_labels < ARRAY_SIZE(labels)); - label->offset = num_instructions; + label->offset = instr_offset; label->label = str; } +void +decl_jumptbl(void) +{ + struct afuc_instr *ai = &instructions[num_instructions++]; + assert(num_instructions < ARRAY_SIZE(instructions)); + ai->opc = OPC_JUMPTBL; + instr_offset += 0x80; +} + static int resolve_label(const char *str) { @@ -128,6 +140,30 @@ resolve_label(const char *str) exit(2); } +static void +emit_jumptable(int outfd) +{ + uint32_t jmptable[0x80] = {0}; + int i; + + for (i = 0; i < num_labels; i++) { + struct asm_label *label = &labels[i]; + int id = afuc_pm4_id(label->label); + + /* if it doesn't match a known PM4 packet-id, try to match UNKN%d: */ + if (id < 0) { + if (sscanf(label->label, "UNKN%d", &id) != 1) { + /* if still not found, must not belong in jump-table: */ + continue; + } + } + + jmptable[id] = label->offset; + } + + write(outfd, jmptable, sizeof(jmptable)); +} + static void emit_instructions(int outfd) { @@ -185,16 +221,15 @@ emit_instructions(int outfd) break; } - /* special case, 2nd dword is patched up w/ # of instructions - * (ie. offset of jmptbl) - */ - if (i == 1) { - assert(ai->opc == OPC_RAW_LITERAL); - ai->literal &= ~0xffff; - ai->literal |= num_instructions; + if (ai->opc == OPC_JUMPTBL) { + emit_jumptable(outfd); + continue; } if (ai->opc == OPC_RAW_LITERAL) { + if (ai->label) { + ai->literal = afuc_nop_literal(resolve_label(ai->label), gpuver); + } write(outfd, &ai->literal, 4); continue; } @@ -218,30 +253,6 @@ parse_sqe_reg(const char *name) return afuc_sqe_reg(name + 1); } -static void -emit_jumptable(int outfd) -{ - uint32_t jmptable[0x80] = {0}; - int i; - - for (i = 0; i < num_labels; i++) { - struct asm_label *label = &labels[i]; - int id = afuc_pm4_id(label->label); - - /* if it doesn't match a known PM4 packet-id, try to match UNKN%d: */ - if (id < 0) { - if (sscanf(label->label, "UNKN%d", &id) != 1) { - /* if still not found, must not belong in jump-table: */ - continue; - } - } - - jmptable[id] = label->offset; - } - - write(outfd, jmptable, sizeof(jmptable)); -} - static void usage(void) { @@ -314,7 +325,6 @@ main(int argc, char **argv) } emit_instructions(outfd); - emit_jumptable(outfd); close(outfd); diff --git a/src/freedreno/afuc/asm.h b/src/freedreno/afuc/asm.h index ced8c6a2546..ece309c19ab 100644 --- a/src/freedreno/afuc/asm.h +++ b/src/freedreno/afuc/asm.h @@ -37,6 +37,7 @@ struct asm_label { struct afuc_instr *next_instr(afuc_opc opc); void decl_label(const char *str); +void decl_jumptbl(void); static inline uint32_t parse_reg(const char *str) diff --git a/src/freedreno/afuc/disasm.c b/src/freedreno/afuc/disasm.c index 3a24eeaadb0..13dd2136ae4 100644 --- a/src/freedreno/afuc/disasm.c +++ b/src/freedreno/afuc/disasm.c @@ -191,13 +191,19 @@ post_instr_cb(void *data, unsigned n, void *instr) } } +uint32_t jumptbl_offset = ~0; + /* Assume that instructions that don't match are raw data */ static void no_match(FILE *out, const BITSET_WORD *bitset, size_t size) { - fprintf(out, "[%08x]", bitset[0]); - print_gpu_reg(out, bitset[0]); - fprintf(out, "\n"); + if (jumptbl_offset != ~0 && bitset[0] == afuc_nop_literal(jumptbl_offset, gpuver)) { + fprintf(out, "[#jumptbl]\n"); + } else { + fprintf(out, "[%08x]", bitset[0]); + print_gpu_reg(out, bitset[0]); + fprintf(out, "\n"); + } } static void @@ -241,6 +247,25 @@ setup_packet_table(struct isa_decode_options *options, options->entrypoint_count = sizedwords; } +static uint32_t +find_jump_table(uint32_t *instrs, uint32_t sizedwords, + uint32_t *jmptbl, uint32_t jmptbl_size) +{ + for (unsigned i = 0; i <= sizedwords - jmptbl_size; i++) { + bool found = true; + for (unsigned j = 0; j < jmptbl_size; j++) { + if (instrs[i + j] != jmptbl[j]) { + found = false; + break; + } + } + if (found) + return i; + } + + return ~0; +} + static void disasm(struct emu *emu) { @@ -289,6 +314,9 @@ disasm(struct emu *emu) setup_packet_table(&options, emu->jmptbl, ARRAY_SIZE(emu->jmptbl)); + jumptbl_offset = find_jump_table(emu->instrs, sizedwords, emu->jmptbl, + ARRAY_SIZE(emu->jmptbl)); + /* TODO add option to emulate LPAC SQE instead: */ if (emulator) { /* Start from clean slate: */ @@ -302,7 +330,18 @@ disasm(struct emu *emu) } /* print instructions: */ - isa_disasm(emu->instrs, sizedwords * 4, stdout, &options); + isa_disasm(emu->instrs, MIN2(sizedwords, jumptbl_offset) * 4, stdout, &options); + + /* print jump table */ + if (jumptbl_offset != ~0) { + printf("jumptbl:\n"); + printf(".jumptbl\n"); + + if (jumptbl_offset + ARRAY_SIZE(emu->jmptbl) != sizedwords) { + for (unsigned i = jumptbl_offset + ARRAY_SIZE(emu->jmptbl); i < sizedwords; i++) + printf("[%08x]\n", emu->instrs[i]); + } + } if (bv_offset) { printf(";\n"); @@ -322,7 +361,19 @@ disasm(struct emu *emu) uint32_t sizedwords = lpac_offset - bv_offset; - isa_disasm(emu->instrs, sizedwords * 4, stdout, &options); + jumptbl_offset = find_jump_table(emu->instrs, sizedwords, emu->jmptbl, + ARRAY_SIZE(emu->jmptbl)); + + isa_disasm(emu->instrs, MIN2(sizedwords, jumptbl_offset) * 4, stdout, &options); + + if (jumptbl_offset != ~0) { + printf("jumptbl:\n"); + printf(".jumptbl\n"); + if (jumptbl_offset + ARRAY_SIZE(emu->jmptbl) != sizedwords) { + for (unsigned i = jumptbl_offset + ARRAY_SIZE(emu->jmptbl); i < sizedwords; i++) + printf("[%08x]\n", emu->instrs[i]); + } + } emu->instrs -= bv_offset; emu->sizedwords += bv_offset; @@ -344,7 +395,19 @@ disasm(struct emu *emu) setup_packet_table(&options, emu->jmptbl, ARRAY_SIZE(emu->jmptbl)); - isa_disasm(emu->instrs, emu->sizedwords * 4, stdout, &options); + jumptbl_offset = find_jump_table(emu->instrs, emu->sizedwords, emu->jmptbl, + ARRAY_SIZE(emu->jmptbl)); + + isa_disasm(emu->instrs, MIN2(emu->sizedwords, jumptbl_offset) * 4, stdout, &options); + + if (jumptbl_offset != ~0) { + printf("jumptbl:\n"); + printf(".jumptbl\n"); + if (jumptbl_offset + ARRAY_SIZE(emu->jmptbl) != emu->sizedwords) { + for (unsigned i = jumptbl_offset + ARRAY_SIZE(emu->jmptbl); i < emu->sizedwords; i++) + printf("[%08x]\n", emu->instrs[i]); + } + } emu->instrs -= lpac_offset; emu->sizedwords += lpac_offset; diff --git a/src/freedreno/afuc/lexer.l b/src/freedreno/afuc/lexer.l index 333e732bb79..ba227044c64 100644 --- a/src/freedreno/afuc/lexer.l +++ b/src/freedreno/afuc/lexer.l @@ -98,6 +98,8 @@ extern YYSTYPE yylval; "(sds"[1-3]")" yylval.num = yytext[4] - '0'; return T_SDS; "(peek)" return TOKEN(T_PEEK); +".jumptbl" return TOKEN(T_JUMPTBL); + "," return ','; "[" return '['; "]" return ']'; diff --git a/src/freedreno/afuc/parser.y b/src/freedreno/afuc/parser.y index d1e1ff0ac63..1f9235d7463 100644 --- a/src/freedreno/afuc/parser.y +++ b/src/freedreno/afuc/parser.y @@ -173,6 +173,8 @@ label(const char *str) %token T_XMOV %token T_SDS +%token T_JUMPTBL + %type reg %type immediate %type xmov @@ -188,10 +190,11 @@ instrs: instrs instr_or_label | instr_or_label instr_or_label: instr_r -| T_REP instr_r { instr->rep = true; } +| T_REP instr_r { instr->rep = true; } | branch_instr | other_instr -| T_LABEL_DECL { decl_label($1); } +| T_LABEL_DECL { decl_label($1); } +| T_JUMPTBL { decl_jumptbl(); } xmov: T_XMOV { $$ = $1; } | { $$ = 0; } @@ -295,6 +298,7 @@ other_instr: T_OP_CALL T_LABEL_REF { new_instr(OPC_CALL); label($2); } | T_OP_WAITIN { new_instr(OPC_WAITIN); } | T_OP_NOP { new_instr(OPC_NOP); } | T_LITERAL { new_instr(OPC_RAW_LITERAL); literal($1); } +| '[' T_LABEL_REF ']' { new_instr(OPC_RAW_LITERAL); label($2); } reg: T_REGISTER