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 <robdclark@chromium.org> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/10944>
This commit is contained in:
parent
33b9445a68
commit
d367d84d87
|
@ -300,6 +300,412 @@ print_control_reg(uint32_t id)
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
disasm_instr(uint32_t *instrs, unsigned pc)
|
||||
{
|
||||
int jump_label_idx;
|
||||
afuc_instr *instr = (void *)&instrs[pc];
|
||||
const char *fname, *lname;
|
||||
afuc_opc opc;
|
||||
bool rep;
|
||||
|
||||
afuc_get_opc(instr, &opc, &rep);
|
||||
|
||||
lname = label_name(pc, false);
|
||||
fname = fxn_name(pc);
|
||||
jump_label_idx = get_jump_table_entry(pc);
|
||||
|
||||
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 (fname) {
|
||||
printlbl("%s", fname);
|
||||
printf(":\n");
|
||||
}
|
||||
|
||||
if (lname) {
|
||||
printlbl(" %s", lname);
|
||||
printf(":");
|
||||
} else {
|
||||
printf(" ");
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
printf("\t%04x: %08x ", pc, instrs[pc]);
|
||||
} else {
|
||||
printf(" ");
|
||||
}
|
||||
|
||||
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]);
|
||||
|
||||
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) {
|
||||
if (instr->alui.src && !src1)
|
||||
printerr(" (src=%02x)", instr->alui.src);
|
||||
}
|
||||
|
||||
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..
|
||||
*/
|
||||
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;
|
||||
}
|
||||
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 ");
|
||||
src1 = false;
|
||||
} else {
|
||||
print_alu_name(instr->alu.alu, instrs[pc]);
|
||||
}
|
||||
|
||||
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)
|
||||
printerr(" (pad=%01x)", instr->alu.pad);
|
||||
if (instr->alu.src1 && !src1)
|
||||
printerr(" (src1=%02x)", instr->alu.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.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);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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[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)
|
||||
{
|
||||
|
@ -350,407 +756,7 @@ disasm(uint32_t *buf, int sizedwords)
|
|||
|
||||
/* 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);
|
||||
|
||||
lname = label_name(i, false);
|
||||
fname = fxn_name(i);
|
||||
jump_label_idx = get_jump_table_entry(i);
|
||||
|
||||
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 (fname) {
|
||||
printlbl("%s", fname);
|
||||
printf(":\n");
|
||||
}
|
||||
|
||||
if (lname) {
|
||||
printlbl(" %s", lname);
|
||||
printf(":");
|
||||
} else {
|
||||
printf(" ");
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
printf("\t%04x: %08x ", i, instrs[i]);
|
||||
} else {
|
||||
printf(" ");
|
||||
}
|
||||
|
||||
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[i] != nop) {
|
||||
printerr("[%08x]", instrs[i]);
|
||||
printf(" ; ");
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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..
|
||||
*/
|
||||
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;
|
||||
}
|
||||
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[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]);
|
||||
}
|
||||
|
||||
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)
|
||||
printerr(" (pad=%01x)", instr->alu.pad);
|
||||
if (instr->alu.src1 && !src1)
|
||||
printerr(" (src1=%02x)", instr->alu.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.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);
|
||||
}
|
||||
}
|
||||
|
||||
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 = 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);
|
||||
break;
|
||||
case OPC_PREEMPTLEAVE6:
|
||||
if (gpuver < 6) {
|
||||
printf("[%08x] ; op38", instrs[i]);
|
||||
} 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[i]);
|
||||
printf("setsecure $02, #");
|
||||
printlbl("%s", label_name(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");
|
||||
disasm_instr(instrs, i);
|
||||
}
|
||||
|
||||
/* print jumptable: */
|
||||
|
|
Loading…
Reference in New Issue