2 * Copyright (c) 2017 Rob Clark <robdclark@gmail.com>
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
43 static struct rnndeccontext
*ctx
;
44 static struct rnndb
*db
;
45 static struct rnndomain
*control_regs
;
46 struct rnndomain
*dom
[2];
49 /* non-verbose mode should output something suitable to feed back into
50 * assembler.. verbose mode has additional output useful for debugging
51 * (like unexpected bits that are set)
53 static bool verbose
= false;
55 static void print_gpu_reg(uint32_t regbase
)
57 struct rnndomain
*d
= NULL
;
62 if (rnndec_checkaddr(ctx
, dom
[0], regbase
, 0))
64 else if (rnndec_checkaddr(ctx
, dom
[1], regbase
, 0))
68 struct rnndecaddrinfo
*info
= rnndec_decodeaddr(ctx
, d
, regbase
, 0);
70 printf("\t; %s", info
->name
);
78 static void printc(const char *c
, const char *fmt
, ...)
85 printf("%s", ctx
->colors
->reset
);
88 #define printerr(fmt, ...) printc(ctx->colors->err, fmt, ##__VA_ARGS__)
89 #define printlbl(fmt, ...) printc(ctx->colors->btarg, fmt, ##__VA_ARGS__)
91 static void print_reg(unsigned reg
)
93 // XXX seems like *reading* $00 --> literal zero??
94 // seems like read from $1c gives packet remaining len??
95 // $01 current packet header, writing to $01 triggers
96 // parsing header and jumping to appropriate handler.
98 printf("$rem"); /* remainding dwords in packet */
101 else if (reg
== 0x1e)
102 printf("$addr2"); // XXX
103 else if (reg
== 0x1f)
106 printf("$%02x", reg
);
109 static void print_src(unsigned reg
)
114 static void print_dst(unsigned reg
)
119 static void print_alu_name(afuc_opc opc
, uint32_t instr
)
121 if (opc
== OPC_ADD
) {
123 } else if (opc
== OPC_ADDHI
) {
125 } else if (opc
== OPC_SUB
) {
127 } else if (opc
== OPC_SUBHI
) {
129 } else if (opc
== OPC_AND
) {
131 } else if (opc
== OPC_OR
) {
133 } else if (opc
== OPC_XOR
) {
135 } else if (opc
== OPC_NOT
) {
137 } else if (opc
== OPC_SHL
) {
139 } else if (opc
== OPC_USHR
) {
141 } else if (opc
== OPC_ISHR
) {
143 } else if (opc
== OPC_ROT
) {
145 } else if (opc
== OPC_MUL8
) {
147 } else if (opc
== OPC_MIN
) {
149 } else if (opc
== OPC_MAX
) {
151 } else if (opc
== OPC_CMP
) {
153 } else if (opc
== OPC_MSB
) {
156 printerr("[%08x]", instr
);
157 printf(" ; alu%02x ", opc
);
161 static const char *getpm4(uint32_t id
)
163 return rnndec_decode_enum(ctx
, "adreno_pm4_type3_packets", id
);
166 static inline unsigned
167 _odd_parity_bit(unsigned val
)
169 /* See: http://graphics.stanford.edu/~seander/bithacks.html#ParityParallel
170 * note that we want odd parity so 0x6996 is inverted.
176 return (~0x6996 >> val
) & 1;
181 uint32_t num_jump_labels
;
182 uint32_t jump_labels
[256];
186 static void add_jump_table_entry(uint32_t n
, uint32_t offset
)
190 if (n
> 128) /* can't possibly be a PM4 PKT3.. */
193 for (i
= 0; i
< num_jump_labels
; i
++)
194 if (jump_labels
[i
].offset
== offset
)
197 num_jump_labels
= i
+ 1;
198 jump_labels
[i
].offset
= offset
;
199 jump_labels
[i
].num_jump_labels
= 0;
202 jump_labels
[i
].jump_labels
[jump_labels
[i
].num_jump_labels
++] = n
;
203 assert(jump_labels
[i
].num_jump_labels
< 256);
206 static int get_jump_table_entry(uint32_t offset
)
210 for (i
= 0; i
< num_jump_labels
; i
++)
211 if (jump_labels
[i
].offset
== offset
)
217 static uint32_t label_offsets
[0x512];
218 static int num_label_offsets
;
220 static int label_idx(uint32_t offset
, bool create
)
223 for (i
= 0; i
< num_label_offsets
; i
++)
224 if (offset
== label_offsets
[i
])
228 label_offsets
[i
] = offset
;
229 num_label_offsets
= i
+1;
234 label_name(uint32_t offset
, bool allow_jt
)
240 lidx
= get_jump_table_entry(offset
);
243 for (j
= 0; j
< jump_labels
[lidx
].num_jump_labels
; j
++) {
244 uint32_t jump_label
= jump_labels
[lidx
].jump_labels
[j
];
245 const char *str
= getpm4(jump_label
);
249 // if we don't find anything w/ known name, maybe we should
250 // return UNKN%d to at least make it clear that this is some
251 // sort of jump-table entry?
255 lidx
= label_idx(offset
, false);
258 sprintf(name
, "l%03d", lidx
);
263 static uint32_t fxn_offsets
[0x512];
264 static int num_fxn_offsets
;
266 static int fxn_idx(uint32_t offset
, bool create
)
269 for (i
= 0; i
< num_fxn_offsets
; i
++)
270 if (offset
== fxn_offsets
[i
])
274 fxn_offsets
[i
] = offset
;
275 num_fxn_offsets
= i
+1;
280 fxn_name(uint32_t offset
)
283 int fidx
= fxn_idx(offset
, false);
286 sprintf(name
, "fxn%02d", fidx
);
290 static void print_control_reg(uint32_t id
)
292 if (rnndec_checkaddr(ctx
, control_regs
, id
, 0)) {
293 struct rnndecaddrinfo
*info
= rnndec_decodeaddr(ctx
, control_regs
, id
, 0);
294 printf("@%s", info
->name
);
298 printf("0x%03x", id
);
302 static void disasm(uint32_t *buf
, int sizedwords
)
304 uint32_t *instrs
= buf
;
305 const int jmptbl_start
= instrs
[1] & 0xffff;
306 uint32_t *jmptbl
= &buf
[jmptbl_start
];
312 /* parse jumptable: */
313 for (i
= 0; i
< 0x80; i
++) {
314 unsigned offset
= jmptbl
[i
];
315 unsigned n
= i
;// + CP_NOP;
316 add_jump_table_entry(n
, offset
);
319 /* do a pre-pass to find instructions that are potential branch targets,
320 * and add labels for them:
322 for (i
= 0; i
< jmptbl_start
; i
++) {
323 afuc_instr
*instr
= (void *)&instrs
[i
];
325 afuc_get_opc(instr
, &opc
, &rep
);
332 label_idx(i
+ instr
->br
.ioff
, true);
334 case OPC_PREEMPTLEAVE6
:
336 label_idx(instr
->call
.uoff
, true);
339 fxn_idx(instr
->call
.uoff
, true);
342 /* this implicitly jumps to pc + 3 if successful */
343 label_idx(i
+ 3, true);
350 /* print instructions: */
351 for (i
= 0; i
< jmptbl_start
; i
++) {
353 afuc_instr
*instr
= (void *)&instrs
[i
];
354 const char *fname
, *lname
;
358 afuc_get_opc(instr
, &opc
, &rep
);
360 lname
= label_name(i
, false);
362 jump_label_idx
= get_jump_table_entry(i
);
364 if (jump_label_idx
>= 0) {
367 for (j
= 0; j
< jump_labels
[jump_label_idx
].num_jump_labels
; j
++) {
368 uint32_t jump_label
= jump_labels
[jump_label_idx
].jump_labels
[j
];
369 const char *name
= getpm4(jump_label
);
371 printlbl("%s", name
);
373 printlbl("UNKN%d", jump_label
);
380 printlbl("%s", fname
);
385 printlbl(" %s", lname
);
393 printf("\t%04x: %08x ", i
, instrs
[i
]);
400 /* a6xx changed the default immediate, and apparently 0
403 const uint32_t nop
= gpuver
>= 6 ? 0x1000000 : 0x0;
404 if (instrs
[i
] != nop
) {
405 printerr("[%08x]", instrs
[i
]);
411 print_gpu_reg(instrs
[i
]);
439 print_alu_name(opc
, instrs
[i
]);
440 print_dst(instr
->alui
.dst
);
443 print_src(instr
->alui
.src
);
446 printf("0x%04x", instr
->alui
.uimm
);
447 print_gpu_reg(instr
->alui
.uimm
);
449 /* print out unexpected bits: */
451 if (instr
->alui
.src
&& !src1
)
452 printerr(" (src=%02x)", instr
->alui
.src
);
461 print_dst(instr
->movi
.dst
);
462 printf(", 0x%04x", instr
->movi
.uimm
);
463 if (instr
->movi
.shift
)
464 printf(" << %u", instr
->movi
.shift
);
466 /* using mov w/ << 16 is popular way to construct a pkt7
467 * header to send (for ex, from PFP to ME), so check that
470 if ((instr
->movi
.shift
== 16) &&
471 ((instr
->movi
.uimm
& 0xff00) == 0x7000)) {
474 opc
= instr
->movi
.uimm
& 0x7f;
475 p
= _odd_parity_bit(opc
);
477 /* So, you'd think that checking the parity bit would be
478 * a good way to rule out false positives, but seems like
479 * ME doesn't really care.. at least it would filter out
480 * things that look like actual legit packets between
483 if (1 || p
== ((instr
->movi
.uimm
>> 7) & 0x1)) {
484 const char *name
= getpm4(opc
);
487 printlbl("%s", name
);
489 printlbl("UNKN%u", opc
);
494 print_gpu_reg(instr
->movi
.uimm
<< instr
->movi
.shift
);
501 if (instr
->alu
.alu
== OPC_NOT
|| instr
->alu
.alu
== OPC_MSB
)
505 printf("[%08x] ; ", instrs
[i
]);
510 /* special case mnemonics:
511 * reading $00 seems to always yield zero, and so:
512 * or $dst, $00, $src -> mov $dst, $src
513 * Maybe add one for negate too, ie.
514 * sub $dst, $00, $src ???
516 if ((instr
->alu
.alu
== OPC_OR
) && !instr
->alu
.src1
) {
520 print_alu_name(instr
->alu
.alu
, instrs
[i
]);
523 print_dst(instr
->alu
.dst
);
526 print_src(instr
->alu
.src1
);
529 print_src(instr
->alu
.src2
);
531 /* print out unexpected bits: */
534 printerr(" (pad=%03x)", instr
->alu
.pad
);
535 if (instr
->alu
.src1
&& !src1
)
536 printerr(" (src1=%02x)", instr
->alu
.src1
);
547 bool is_control_reg
= true;
557 is_control_reg
= false;
561 is_control_reg
= false;
565 assert(!"unreachable");
576 fprintf(stderr
, "A6xx control opcode on A5xx?\n");
581 print_src(instr
->control
.src1
);
583 print_src(instr
->control
.src2
);
585 if (is_control_reg
&& instr
->control
.flags
!= 0x4)
586 print_control_reg(instr
->control
.uimm
);
588 printf("0x%03x", instr
->control
.uimm
);
589 printf("], 0x%x", instr
->control
.flags
);
596 unsigned off
= i
+ instr
->br
.ioff
;
600 /* Since $00 reads back zero, it can be used as src for
601 * unconditional branches. (This only really makes sense
602 * for the BREQB.. or possible BRNEI if imm==0.)
604 * If bit=0 then branch is taken if *all* bits are zero.
605 * Otherwise it is taken if bit (bit-1) is clear.
607 * Note the instruction after a jump/branch is executed
608 * regardless of whether branch is taken, so use nop or
609 * take that into account in code.
611 if (instr
->br
.src
|| (opc
!= OPC_BRNEB
)) {
614 if (opc
== OPC_BRNEI
) {
617 } else if (opc
== OPC_BREQI
) {
620 } else if (opc
== OPC_BRNEB
) {
622 } else if (opc
== OPC_BREQB
) {
625 print_src(instr
->br
.src
);
627 printf(", 0x%x,", instr
->br
.bit_or_imm
);
629 printf(", b%u,", instr
->br
.bit_or_imm
);
633 if (verbose
&& instr
->br
.bit_or_imm
) {
634 printerr(" (src=%03x, bit=%03x) ",
635 instr
->br
.src
, instr
->br
.bit_or_imm
);
640 printlbl("%s", label_name(off
, true));
642 printf(" (#%d, %04x)", instr
->br
.ioff
, off
);
648 printlbl("%s", fxn_name(instr
->call
.uoff
));
650 printf(" (%04x)", instr
->call
.uoff
);
651 if (instr
->br
.bit_or_imm
|| instr
->br
.src
) {
652 printerr(" (src=%03x, bit=%03x) ",
653 instr
->br
.src
, instr
->br
.bit_or_imm
);
660 printf("[%08x] ; ", instrs
[i
]);
661 if (instr
->ret
.interrupt
)
668 if (instr
->waitin
.pad
)
669 printf("[%08x] ; ", instrs
[i
]);
671 if (verbose
&& instr
->waitin
.pad
)
672 printerr(" (pad=%x)", instr
->waitin
.pad
);
674 case OPC_PREEMPTLEAVE6
:
676 printf("[%08x] ; op38", instrs
[i
]);
678 printf("preemptleave #");
679 printlbl("%s", label_name(instr
->call
.uoff
, true));
683 /* Note: This seems to implicitly read the secure/not-secure state
684 * to set from the low bit of $02, and implicitly jumps to pc + 3
685 * (i.e. skipping the next two instructions) if it succeeds. We
686 * print these implicit parameters to make reading the disassembly
690 printf("[%08x] ; ", instrs
[i
]);
691 printf("setsecure $02, #");
692 printlbl("%s", label_name(i
+ 3, true));
695 printerr("[%08x]", instrs
[i
]);
696 printf(" ; op%02x ", opc
);
697 print_dst(instr
->alui
.dst
);
699 print_src(instr
->alui
.src
);
700 print_gpu_reg(instrs
[i
] & 0xffff);
706 /* print jumptable: */
708 printf(";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n");
709 printf("; JUMP TABLE\n");
710 for (i
= 0; i
< 0x7f; i
++) {
711 int n
= i
;// + CP_NOP;
712 uint32_t offset
= jmptbl
[i
];
713 const char *name
= getpm4(n
);
714 printf("%3d %02x: ", n
, n
);
715 printf("%04x", offset
);
717 printf(" ; %s", name
);
719 printf(" ; UNKN%d", n
);
726 #define CHUNKSIZE 4096
728 static char * readfile(const char *path
, int *sz
)
733 fd
= open(path
, O_RDONLY
);
738 buf
= realloc(buf
, n
+ CHUNKSIZE
);
739 ret
= read(fd
, buf
+ n
, CHUNKSIZE
);
744 } else if (ret
< CHUNKSIZE
) {
754 static void usage(void)
756 fprintf(stderr
, "Usage:\n"
757 "\tdisasm [-g GPUVER] [-v] [-c] filename.asm\n"
758 "\t\t-g - specify GPU version (5, etc)\n"
759 "\t\t-c - use colors\n"
760 "\t\t-v - verbose output\n"
765 int main(int argc
, char **argv
)
768 char *file
, *control_reg_name
;
772 /* Argument parsing: */
773 while ((c
= getopt (argc
, argv
, "g:vc")) != -1) {
776 gpuver
= atoi(optarg
);
789 if (optind
>= argc
) {
790 fprintf(stderr
, "no file specified!\n");
796 /* if gpu version not specified, infer from filename: */
798 if (strstr(file
, "a5")) {
800 } else if (strstr(file
, "a6")) {
807 printf("; a6xx microcode\n");
809 control_reg_name
= "A6XX_CONTROL_REG";
812 printf("; a5xx microcode\n");
814 control_reg_name
= "A5XX_CONTROL_REG";
817 fprintf(stderr
, "unknown GPU version!\n");
824 ctx
= rnndec_newcontext(db
);
825 ctx
->colors
= colors
? &envy_def_colors
: &envy_null_colors
;
827 rnn_parsefile(db
, "adreno.xml");
830 errx(db
->estatus
, "failed to parse register database");
831 dom
[0] = rnn_finddomain(db
, variant
);
832 dom
[1] = rnn_finddomain(db
, "AXXX");
833 control_regs
= rnn_finddomain(db
, control_reg_name
);
835 rnndec_varadd(ctx
, "chip", variant
);
837 buf
= (uint32_t *)readfile(file
, &sz
);
839 printf("; Disassembling microcode: %s\n", file
);
840 printf("; Version: %08x\n\n", buf
[1]);
841 disasm(&buf
[1], sz
/4 - 1);