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: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/26771>
This commit is contained in:
Connor Abbott
2023-12-18 14:02:53 -05:00
committed by Marge Bot
parent 46681d64f6
commit 542ae9de01
8 changed files with 139 additions and 171 deletions
@@ -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
@@ -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
+11
View File
@@ -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);
+43 -33
View File
@@ -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);
+1
View File
@@ -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)
+69 -6
View File
@@ -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;
+2
View File
@@ -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 ']';
+6 -2
View File
@@ -173,6 +173,8 @@ label(const char *str)
%token <num> T_XMOV
%token <num> T_SDS
%token <tok> T_JUMPTBL
%type <num> reg
%type <num> immediate
%type <num> 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