From d367d84d8765d75726279ece43f481673bf5a454 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Sun, 16 May 2021 12:15:31 -0700 Subject: [PATCH] freedreno/afuc: Split out instruction decode helper Split the giant switch/decode out into a helper function so that we can re-use it for emulator mode. Signed-off-by: Rob Clark Part-of: --- src/freedreno/afuc/disasm.c | 806 ++++++++++++++++++++++---------------------- 1 file changed, 406 insertions(+), 400 deletions(-) diff --git a/src/freedreno/afuc/disasm.c b/src/freedreno/afuc/disasm.c index 14e9d2f..cefb602 100644 --- a/src/freedreno/afuc/disasm.c +++ b/src/freedreno/afuc/disasm.c @@ -301,456 +301,462 @@ print_control_reg(uint32_t id) } static void -disasm(uint32_t *buf, int sizedwords) +disasm_instr(uint32_t *instrs, unsigned pc) { - uint32_t *instrs = buf; - const int jmptbl_start = instrs[1] & 0xffff; - uint32_t *jmptbl = &buf[jmptbl_start]; + int jump_label_idx; + afuc_instr *instr = (void *)&instrs[pc]; + const char *fname, *lname; afuc_opc opc; bool rep; - int i; - /* parse jumptable: */ - for (i = 0; i < 0x80; i++) { - unsigned offset = jmptbl[i]; - unsigned n = i; // + CP_NOP; - add_jump_table_entry(n, offset); - } + afuc_get_opc(instr, &opc, &rep); - /* do a pre-pass to find instructions that are potential branch targets, - * and add labels for them: - */ - for (i = 0; i < jmptbl_start; i++) { - afuc_instr *instr = (void *)&instrs[i]; - - afuc_get_opc(instr, &opc, &rep); + lname = label_name(pc, false); + fname = fxn_name(pc); + jump_label_idx = get_jump_table_entry(pc); - switch (opc) { - case OPC_BRNEI: - case OPC_BREQI: - case OPC_BRNEB: - case OPC_BREQB: - label_idx(i + instr->br.ioff, true); - break; - case OPC_PREEMPTLEAVE6: - if (gpuver >= 6) - label_idx(instr->call.uoff, true); - break; - case OPC_CALL: - fxn_idx(instr->call.uoff, true); - break; - case OPC_SETSECURE: - /* this implicitly jumps to pc + 3 if successful */ - label_idx(i + 3, true); - break; - default: - break; + if (jump_label_idx >= 0) { + int j; + printf("\n"); + for (j = 0; j < jump_labels[jump_label_idx].num_jump_labels; j++) { + uint32_t jump_label = jump_labels[jump_label_idx].jump_labels[j]; + const char *name = getpm4(jump_label); + if (name) { + printlbl("%s", name); + } else { + printlbl("UNKN%d", jump_label); + } + printf(":\n"); } } - /* print instructions: */ - for (i = 0; i < jmptbl_start; i++) { - int jump_label_idx; - afuc_instr *instr = (void *)&instrs[i]; - const char *fname, *lname; - afuc_opc opc; - bool rep; - - afuc_get_opc(instr, &opc, &rep); + if (fname) { + printlbl("%s", fname); + printf(":\n"); + } - lname = label_name(i, false); - fname = fxn_name(i); - jump_label_idx = get_jump_table_entry(i); + if (lname) { + printlbl(" %s", lname); + printf(":"); + } else { + printf(" "); + } - if (jump_label_idx >= 0) { - int j; - printf("\n"); - for (j = 0; j < jump_labels[jump_label_idx].num_jump_labels; j++) { - uint32_t jump_label = jump_labels[jump_label_idx].jump_labels[j]; - const char *name = getpm4(jump_label); - if (name) { - printlbl("%s", name); - } else { - printlbl("UNKN%d", jump_label); - } - printf(":\n"); - } - } + if (verbose) { + printf("\t%04x: %08x ", pc, instrs[pc]); + } else { + printf(" "); + } - if (fname) { - printlbl("%s", fname); - printf(":\n"); + switch (opc) { + case OPC_NOP: { + /* a6xx changed the default immediate, and apparently 0 + * is illegal now. + */ + const uint32_t nop = gpuver >= 6 ? 0x1000000 : 0x0; + if (instrs[pc] != nop) { + printerr("[%08x]", instrs[pc]); + printf(" ; "); } + if (rep) + printf("(rep)"); + printf("nop"); + print_gpu_reg(instrs[pc]); - if (lname) { - printlbl(" %s", lname); - printf(":"); - } else { - printf(" "); + break; + } + case OPC_ADD: + case OPC_ADDHI: + case OPC_SUB: + case OPC_SUBHI: + case OPC_AND: + case OPC_OR: + case OPC_XOR: + case OPC_NOT: + case OPC_SHL: + case OPC_USHR: + case OPC_ISHR: + case OPC_ROT: + case OPC_MUL8: + case OPC_MIN: + case OPC_MAX: + case OPC_CMP: { + bool src1 = true; + + if (opc == OPC_NOT) + src1 = false; + + if (rep) + printf("(rep)"); + + print_alu_name(opc, instrs[pc]); + print_dst(instr->alui.dst); + printf(", "); + if (src1) { + print_src(instr->alui.src); + printf(", "); } + printf("0x%04x", instr->alui.uimm); + print_gpu_reg(instr->alui.uimm); + /* print out unexpected bits: */ if (verbose) { - printf("\t%04x: %08x ", i, instrs[i]); - } else { - printf(" "); + if (instr->alui.src && !src1) + printerr(" (src=%02x)", instr->alui.src); } - switch (opc) { - case OPC_NOP: { - /* a6xx changed the default immediate, and apparently 0 - * is illegal now. + break; + } + case OPC_MOVI: { + if (rep) + printf("(rep)"); + printf("mov "); + print_dst(instr->movi.dst); + printf(", 0x%04x", instr->movi.uimm); + if (instr->movi.shift) + printf(" << %u", instr->movi.shift); + + /* using mov w/ << 16 is popular way to construct a pkt7 + * header to send (for ex, from PFP to ME), so check that + * case first + */ + if ((instr->movi.shift == 16) && + ((instr->movi.uimm & 0xff00) == 0x7000)) { + unsigned opc, p; + + opc = instr->movi.uimm & 0x7f; + p = pm4_odd_parity_bit(opc); + + /* So, you'd think that checking the parity bit would be + * a good way to rule out false positives, but seems like + * ME doesn't really care.. at least it would filter out + * things that look like actual legit packets between + * PFP and ME.. */ - const uint32_t nop = gpuver >= 6 ? 0x1000000 : 0x0; - if (instrs[i] != nop) { - printerr("[%08x]", instrs[i]); - printf(" ; "); + if (1 || p == ((instr->movi.uimm >> 7) & 0x1)) { + const char *name = getpm4(opc); + printf("\t; "); + if (name) + printlbl("%s", name); + else + printlbl("UNKN%u", opc); + break; } - if (rep) - printf("(rep)"); - printf("nop"); - print_gpu_reg(instrs[i]); - - break; } - case OPC_ADD: - case OPC_ADDHI: - case OPC_SUB: - case OPC_SUBHI: - case OPC_AND: - case OPC_OR: - case OPC_XOR: - case OPC_NOT: - case OPC_SHL: - case OPC_USHR: - case OPC_ISHR: - case OPC_ROT: - case OPC_MUL8: - case OPC_MIN: - case OPC_MAX: - case OPC_CMP: { - bool src1 = true; - - if (opc == OPC_NOT) - src1 = false; - - if (rep) - printf("(rep)"); - - print_alu_name(opc, instrs[i]); - print_dst(instr->alui.dst); - printf(", "); - if (src1) { - print_src(instr->alui.src); - printf(", "); - } - printf("0x%04x", instr->alui.uimm); - print_gpu_reg(instr->alui.uimm); - /* print out unexpected bits: */ - if (verbose) { - if (instr->alui.src && !src1) - printerr(" (src=%02x)", instr->alui.src); - } + print_gpu_reg(instr->movi.uimm << instr->movi.shift); - break; - } - case OPC_MOVI: { - if (rep) - printf("(rep)"); + break; + } + case OPC_ALU: { + bool src1 = true; + + if (instr->alu.alu == OPC_NOT || instr->alu.alu == OPC_MSB) + src1 = false; + + if (instr->alu.pad) + printf("[%08x] ; ", instrs[pc]); + + if (rep) + printf("(rep)"); + if (instr->alu.xmov) + printf("(xmov%d)", instr->alu.xmov); + + /* special case mnemonics: + * reading $00 seems to always yield zero, and so: + * or $dst, $00, $src -> mov $dst, $src + * Maybe add one for negate too, ie. + * sub $dst, $00, $src ??? + */ + if ((instr->alu.alu == OPC_OR) && !instr->alu.src1) { printf("mov "); - print_dst(instr->movi.dst); - printf(", 0x%04x", instr->movi.uimm); - if (instr->movi.shift) - printf(" << %u", instr->movi.shift); - - /* using mov w/ << 16 is popular way to construct a pkt7 - * header to send (for ex, from PFP to ME), so check that - * case first - */ - if ((instr->movi.shift == 16) && - ((instr->movi.uimm & 0xff00) == 0x7000)) { - unsigned opc, p; - - opc = instr->movi.uimm & 0x7f; - p = pm4_odd_parity_bit(opc); - - /* So, you'd think that checking the parity bit would be - * a good way to rule out false positives, but seems like - * ME doesn't really care.. at least it would filter out - * things that look like actual legit packets between - * PFP and ME.. - */ - if (1 || p == ((instr->movi.uimm >> 7) & 0x1)) { - const char *name = getpm4(opc); - printf("\t; "); - if (name) - printlbl("%s", name); - else - printlbl("UNKN%u", opc); - break; - } - } - - print_gpu_reg(instr->movi.uimm << instr->movi.shift); - - break; + src1 = false; + } else { + print_alu_name(instr->alu.alu, instrs[pc]); } - case OPC_ALU: { - bool src1 = true; - if (instr->alu.alu == OPC_NOT || instr->alu.alu == OPC_MSB) - src1 = false; + print_dst(instr->alu.dst); + if (src1) { + printf(", "); + print_src(instr->alu.src1); + } + printf(", "); + print_src(instr->alu.src2); + /* print out unexpected bits: */ + if (verbose) { if (instr->alu.pad) - printf("[%08x] ; ", instrs[i]); - - if (rep) - printf("(rep)"); - if (instr->alu.xmov) - printf("(xmov%d)", instr->alu.xmov); - - /* special case mnemonics: - * reading $00 seems to always yield zero, and so: - * or $dst, $00, $src -> mov $dst, $src - * Maybe add one for negate too, ie. - * sub $dst, $00, $src ??? - */ - if ((instr->alu.alu == OPC_OR) && !instr->alu.src1) { - printf("mov "); - src1 = false; - } else { - print_alu_name(instr->alu.alu, instrs[i]); - } + printerr(" (pad=%01x)", instr->alu.pad); + if (instr->alu.src1 && !src1) + printerr(" (src1=%02x)", instr->alu.src1); + } - print_dst(instr->alu.dst); - if (src1) { + /* xmov is a modifier that makes the processor execute up to 3 + * extra mov's after the current instruction. Given an ALU + * instruction: + * + * (xmovN) alu $dst, $src1, $src2 + * + * In all of the uses in the firmware blob, $dst and $src2 are one + * of the "special" registers $data, $addr, $addr2. I've observed + * that if $dst isn't "special" then it's replaced with $00 + * instead of $data, but I haven't checked what happens if $src2 + * isn't "special". Anyway, in the usual case, the HW produces a + * count M = min(N, $rem) and then does the following: + * + * M = 1: + * mov $data, $src2 + * + * M = 2: + * mov $data, $src2 + * mov $data, $src2 + * + * M = 3: + * mov $data, $src2 + * mov $dst, $src2 (special case for CP_CONTEXT_REG_BUNCH) + * mov $data, $src2 + * + * It seems to be frequently used in combination with (rep) to + * provide a kind of hardware-based loop unrolling, and there's + * even a special case in the ISA to be able to do this with + * CP_CONTEXT_REG_BUNCH. However (rep) isn't required. + * + * This dumps the expected extra instructions, assuming that $rem + * isn't too small. + */ + if (verbose && instr->alu.xmov) { + for (int i = 0; i < instr->alu.xmov; i++) { + printf("\n ; mov "); + if (instr->alu.dst < 0x1d) + printf("$00"); + else if (instr->alu.xmov == 3 && i == 1) + print_dst(instr->alu.dst); + else + printf("$data"); printf(", "); - print_src(instr->alu.src1); - } - printf(", "); - print_src(instr->alu.src2); - - /* print out unexpected bits: */ - if (verbose) { - if (instr->alu.pad) - printerr(" (pad=%01x)", instr->alu.pad); - if (instr->alu.src1 && !src1) - printerr(" (src1=%02x)", instr->alu.src1); + print_src(instr->alu.src2); } + } - /* xmov is a modifier that makes the processor execute up to 3 - * extra mov's after the current instruction. Given an ALU - * instruction: - * - * (xmovN) alu $dst, $src1, $src2 - * - * In all of the uses in the firmware blob, $dst and $src2 are one - * of the "special" registers $data, $addr, $addr2. I've observed - * that if $dst isn't "special" then it's replaced with $00 - * instead of $data, but I haven't checked what happens if $src2 - * isn't "special". Anyway, in the usual case, the HW produces a - * count M = min(N, $rem) and then does the following: - * - * M = 1: - * mov $data, $src2 - * - * M = 2: - * mov $data, $src2 - * mov $data, $src2 - * - * M = 3: - * mov $data, $src2 - * mov $dst, $src2 (special case for CP_CONTEXT_REG_BUNCH) - * mov $data, $src2 - * - * It seems to be frequently used in combination with (rep) to - * provide a kind of hardware-based loop unrolling, and there's - * even a special case in the ISA to be able to do this with - * CP_CONTEXT_REG_BUNCH. However (rep) isn't required. - * - * This dumps the expected extra instructions, assuming that $rem - * isn't too small. - */ - if (verbose && instr->alu.xmov) { - for (int i = 0; i < instr->alu.xmov; i++) { - printf("\n ; mov "); - if (instr->alu.dst < 0x1d) - printf("$00"); - else if (instr->alu.xmov == 3 && i == 1) - print_dst(instr->alu.dst); - else - printf("$data"); - printf(", "); - print_src(instr->alu.src2); - } + break; + } + case OPC_CWRITE6: + case OPC_CREAD6: + case OPC_STORE6: + case OPC_LOAD6: { + if (rep) + printf("(rep)"); + + bool is_control_reg = true; + if (gpuver >= 6) { + switch (opc) { + case OPC_CWRITE6: + printf("cwrite "); + break; + case OPC_CREAD6: + printf("cread "); + break; + case OPC_STORE6: + is_control_reg = false; + printf("store "); + break; + case OPC_LOAD6: + is_control_reg = false; + printf("load "); + break; + default: + assert(!"unreachable"); + } + } else { + switch (opc) { + case OPC_CWRITE5: + printf("cwrite "); + break; + case OPC_CREAD5: + printf("cread "); + break; + default: + fprintf(stderr, "A6xx control opcode on A5xx?\n"); + exit(1); } - - break; } - case OPC_CWRITE6: - case OPC_CREAD6: - case OPC_STORE6: - case OPC_LOAD6: { - if (rep) - printf("(rep)"); - - bool is_control_reg = true; - if (gpuver >= 6) { - switch (opc) { - case OPC_CWRITE6: - printf("cwrite "); - break; - case OPC_CREAD6: - printf("cread "); - break; - case OPC_STORE6: - is_control_reg = false; - printf("store "); - break; - case OPC_LOAD6: - is_control_reg = false; - printf("load "); - break; - default: - assert(!"unreachable"); - } + + print_src(instr->control.src1); + printf(", ["); + print_src(instr->control.src2); + printf(" + "); + if (is_control_reg && instr->control.flags != 0x4) + print_control_reg(instr->control.uimm); + else + printf("0x%03x", instr->control.uimm); + printf("], 0x%x", instr->control.flags); + break; + } + case OPC_BRNEI: + case OPC_BREQI: + case OPC_BRNEB: + case OPC_BREQB: { + unsigned off = pc + instr->br.ioff; + + assert(!rep); + + /* Since $00 reads back zero, it can be used as src for + * unconditional branches. (This only really makes sense + * for the BREQB.. or possible BRNEI if imm==0.) + * + * If bit=0 then branch is taken if *all* bits are zero. + * Otherwise it is taken if bit (bit-1) is clear. + * + * Note the instruction after a jump/branch is executed + * regardless of whether branch is taken, so use nop or + * take that into account in code. + */ + if (instr->br.src || (opc != OPC_BRNEB)) { + bool immed = false; + + if (opc == OPC_BRNEI) { + printf("brne "); + immed = true; + } else if (opc == OPC_BREQI) { + printf("breq "); + immed = true; + } else if (opc == OPC_BRNEB) { + printf("brne "); + } else if (opc == OPC_BREQB) { + printf("breq "); + } + print_src(instr->br.src); + if (immed) { + printf(", 0x%x,", instr->br.bit_or_imm); } else { - switch (opc) { - case OPC_CWRITE5: - printf("cwrite "); - break; - case OPC_CREAD5: - printf("cread "); - break; - default: - fprintf(stderr, "A6xx control opcode on A5xx?\n"); - exit(1); - } + printf(", b%u,", instr->br.bit_or_imm); + } + } else { + printf("jump"); + if (verbose && instr->br.bit_or_imm) { + printerr(" (src=%03x, bit=%03x) ", instr->br.src, + instr->br.bit_or_imm); } + } - print_src(instr->control.src1); - printf(", ["); - print_src(instr->control.src2); - printf(" + "); - if (is_control_reg && instr->control.flags != 0x4) - print_control_reg(instr->control.uimm); - else - printf("0x%03x", instr->control.uimm); - printf("], 0x%x", instr->control.flags); - break; + printf(" #"); + printlbl("%s", label_name(off, true)); + if (verbose) + printf(" (#%d, %04x)", instr->br.ioff, off); + break; + } + case OPC_CALL: + assert(!rep); + printf("call #"); + printlbl("%s", fxn_name(instr->call.uoff)); + if (verbose) { + printf(" (%04x)", instr->call.uoff); + if (instr->br.bit_or_imm || instr->br.src) { + printerr(" (src=%03x, bit=%03x) ", instr->br.src, + instr->br.bit_or_imm); + } + } + break; + case OPC_RET: + assert(!rep); + if (instr->ret.pad) + printf("[%08x] ; ", instrs[pc]); + if (instr->ret.interrupt) + printf("iret"); + else + printf("ret"); + break; + case OPC_WIN: + assert(!rep); + if (instr->waitin.pad) + printf("[%08x] ; ", instrs[pc]); + printf("waitin"); + if (verbose && instr->waitin.pad) + printerr(" (pad=%x)", instr->waitin.pad); + break; + case OPC_PREEMPTLEAVE6: + if (gpuver < 6) { + printf("[%08x] ; op38", instrs[pc]); + } else { + printf("preemptleave #"); + printlbl("%s", label_name(instr->call.uoff, true)); } + break; + case OPC_SETSECURE: + /* Note: This seems to implicitly read the secure/not-secure state + * to set from the low bit of $02, and implicitly jumps to pc + 3 + * (i.e. skipping the next two instructions) if it succeeds. We + * print these implicit parameters to make reading the disassembly + * easier. + */ + if (instr->pad) + printf("[%08x] ; ", instrs[pc]); + printf("setsecure $02, #"); + printlbl("%s", label_name(pc + 3, true)); + break; + default: + printerr("[%08x]", instrs[pc]); + printf(" ; op%02x ", opc); + print_dst(instr->alui.dst); + printf(", "); + print_src(instr->alui.src); + print_gpu_reg(instrs[pc] & 0xffff); + break; + } + printf("\n"); +} + +static void +disasm(uint32_t *buf, int sizedwords) +{ + uint32_t *instrs = buf; + const int jmptbl_start = instrs[1] & 0xffff; + uint32_t *jmptbl = &buf[jmptbl_start]; + afuc_opc opc; + bool rep; + int i; + + /* parse jumptable: */ + for (i = 0; i < 0x80; i++) { + unsigned offset = jmptbl[i]; + unsigned n = i; // + CP_NOP; + add_jump_table_entry(n, offset); + } + + /* do a pre-pass to find instructions that are potential branch targets, + * and add labels for them: + */ + for (i = 0; i < jmptbl_start; i++) { + afuc_instr *instr = (void *)&instrs[i]; + + afuc_get_opc(instr, &opc, &rep); + + switch (opc) { case OPC_BRNEI: case OPC_BREQI: case OPC_BRNEB: - case OPC_BREQB: { - unsigned off = i + instr->br.ioff; - - assert(!rep); - - /* Since $00 reads back zero, it can be used as src for - * unconditional branches. (This only really makes sense - * for the BREQB.. or possible BRNEI if imm==0.) - * - * If bit=0 then branch is taken if *all* bits are zero. - * Otherwise it is taken if bit (bit-1) is clear. - * - * Note the instruction after a jump/branch is executed - * regardless of whether branch is taken, so use nop or - * take that into account in code. - */ - if (instr->br.src || (opc != OPC_BRNEB)) { - bool immed = false; - - if (opc == OPC_BRNEI) { - printf("brne "); - immed = true; - } else if (opc == OPC_BREQI) { - printf("breq "); - immed = true; - } else if (opc == OPC_BRNEB) { - printf("brne "); - } else if (opc == OPC_BREQB) { - printf("breq "); - } - print_src(instr->br.src); - if (immed) { - printf(", 0x%x,", instr->br.bit_or_imm); - } else { - printf(", b%u,", instr->br.bit_or_imm); - } - } else { - printf("jump"); - if (verbose && instr->br.bit_or_imm) { - printerr(" (src=%03x, bit=%03x) ", instr->br.src, - instr->br.bit_or_imm); - } - } - - printf(" #"); - printlbl("%s", label_name(off, true)); - if (verbose) - printf(" (#%d, %04x)", instr->br.ioff, off); - break; - } - case OPC_CALL: - assert(!rep); - printf("call #"); - printlbl("%s", fxn_name(instr->call.uoff)); - if (verbose) { - printf(" (%04x)", instr->call.uoff); - if (instr->br.bit_or_imm || instr->br.src) { - printerr(" (src=%03x, bit=%03x) ", instr->br.src, - instr->br.bit_or_imm); - } - } - break; - case OPC_RET: - assert(!rep); - if (instr->ret.pad) - printf("[%08x] ; ", instrs[i]); - if (instr->ret.interrupt) - printf("iret"); - else - printf("ret"); - break; - case OPC_WIN: - assert(!rep); - if (instr->waitin.pad) - printf("[%08x] ; ", instrs[i]); - printf("waitin"); - if (verbose && instr->waitin.pad) - printerr(" (pad=%x)", instr->waitin.pad); + case OPC_BREQB: + label_idx(i + instr->br.ioff, true); break; case OPC_PREEMPTLEAVE6: - if (gpuver < 6) { - printf("[%08x] ; op38", instrs[i]); - } else { - printf("preemptleave #"); - printlbl("%s", label_name(instr->call.uoff, true)); - } + if (gpuver >= 6) + label_idx(instr->call.uoff, true); + break; + case OPC_CALL: + fxn_idx(instr->call.uoff, true); break; case OPC_SETSECURE: - /* Note: This seems to implicitly read the secure/not-secure state - * to set from the low bit of $02, and implicitly jumps to pc + 3 - * (i.e. skipping the next two instructions) if it succeeds. We - * print these implicit parameters to make reading the disassembly - * easier. - */ - if (instr->pad) - printf("[%08x] ; ", instrs[i]); - printf("setsecure $02, #"); - printlbl("%s", label_name(i + 3, true)); + /* this implicitly jumps to pc + 3 if successful */ + label_idx(i + 3, true); break; default: - printerr("[%08x]", instrs[i]); - printf(" ; op%02x ", opc); - print_dst(instr->alui.dst); - printf(", "); - print_src(instr->alui.src); - print_gpu_reg(instrs[i] & 0xffff); break; } - printf("\n"); + } + + /* print instructions: */ + for (i = 0; i < jmptbl_start; i++) { + disasm_instr(instrs, i); } /* print jumptable: */ -- 2.7.4