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 char *getpm4(uint32_t id
)
163 struct rnnenum
*en
= rnn_findenum(ctx
->db
, "adreno_pm4_type3_packets");
166 for (i
= 0; i
< en
->valsnum
; i
++)
167 if (en
->vals
[i
]->valvalid
&& en
->vals
[i
]->value
== id
) {
168 const char *v
= en
->vals
[i
]->varinfo
.variantsstr
;
169 if (v
&& !strstr(v
, variant
))
171 return en
->vals
[i
]->name
;
177 static inline unsigned
178 _odd_parity_bit(unsigned val
)
180 /* See: http://graphics.stanford.edu/~seander/bithacks.html#ParityParallel
181 * note that we want odd parity so 0x6996 is inverted.
187 return (~0x6996 >> val
) & 1;
192 uint32_t num_jump_labels
;
193 uint32_t jump_labels
[256];
197 static void add_jump_table_entry(uint32_t n
, uint32_t offset
)
201 if (n
> 128) /* can't possibly be a PM4 PKT3.. */
204 for (i
= 0; i
< num_jump_labels
; i
++)
205 if (jump_labels
[i
].offset
== offset
)
208 num_jump_labels
= i
+ 1;
209 jump_labels
[i
].offset
= offset
;
210 jump_labels
[i
].num_jump_labels
= 0;
213 jump_labels
[i
].jump_labels
[jump_labels
[i
].num_jump_labels
++] = n
;
214 assert(jump_labels
[i
].num_jump_labels
< 256);
217 static int get_jump_table_entry(uint32_t offset
)
221 for (i
= 0; i
< num_jump_labels
; i
++)
222 if (jump_labels
[i
].offset
== offset
)
228 static uint32_t label_offsets
[0x512];
229 static int num_label_offsets
;
231 static int label_idx(uint32_t offset
, bool create
)
234 for (i
= 0; i
< num_label_offsets
; i
++)
235 if (offset
== label_offsets
[i
])
239 label_offsets
[i
] = offset
;
240 num_label_offsets
= i
+1;
245 label_name(uint32_t offset
, bool allow_jt
)
251 lidx
= get_jump_table_entry(offset
);
254 for (j
= 0; j
< jump_labels
[lidx
].num_jump_labels
; j
++) {
255 uint32_t jump_label
= jump_labels
[lidx
].jump_labels
[j
];
256 char *str
= getpm4(jump_label
);
260 // if we don't find anything w/ known name, maybe we should
261 // return UNKN%d to at least make it clear that this is some
262 // sort of jump-table entry?
266 lidx
= label_idx(offset
, false);
269 sprintf(name
, "l%03d", lidx
);
274 static uint32_t fxn_offsets
[0x512];
275 static int num_fxn_offsets
;
277 static int fxn_idx(uint32_t offset
, bool create
)
280 for (i
= 0; i
< num_fxn_offsets
; i
++)
281 if (offset
== fxn_offsets
[i
])
285 fxn_offsets
[i
] = offset
;
286 num_fxn_offsets
= i
+1;
291 fxn_name(uint32_t offset
)
294 int fidx
= fxn_idx(offset
, false);
297 sprintf(name
, "fxn%02d", fidx
);
301 static void print_control_reg(uint32_t id
)
303 if (rnndec_checkaddr(ctx
, control_regs
, id
, 0)) {
304 struct rnndecaddrinfo
*info
= rnndec_decodeaddr(ctx
, control_regs
, id
, 0);
305 printf("@%s", info
->name
);
309 printf("0x%03x", id
);
313 static void disasm(uint32_t *buf
, int sizedwords
)
315 uint32_t *instrs
= buf
;
316 const int jmptbl_start
= instrs
[1] & 0xffff;
317 uint32_t *jmptbl
= &buf
[jmptbl_start
];
323 /* parse jumptable: */
324 for (i
= 0; i
< 0x80; i
++) {
325 unsigned offset
= jmptbl
[i
];
326 unsigned n
= i
;// + CP_NOP;
327 add_jump_table_entry(n
, offset
);
330 /* do a pre-pass to find instructions that are potential branch targets,
331 * and add labels for them:
333 for (i
= 0; i
< jmptbl_start
; i
++) {
334 afuc_instr
*instr
= (void *)&instrs
[i
];
336 afuc_get_opc(instr
, &opc
, &rep
);
343 label_idx(i
+ instr
->br
.ioff
, true);
345 case OPC_PREEMPTLEAVE6
:
347 label_idx(instr
->call
.uoff
, true);
350 fxn_idx(instr
->call
.uoff
, true);
357 /* print instructions: */
358 for (i
= 0; i
< jmptbl_start
; i
++) {
360 afuc_instr
*instr
= (void *)&instrs
[i
];
361 const char *fname
, *lname
;
365 afuc_get_opc(instr
, &opc
, &rep
);
367 lname
= label_name(i
, false);
369 jump_label_idx
= get_jump_table_entry(i
);
371 if (jump_label_idx
>= 0) {
374 for (j
= 0; j
< jump_labels
[jump_label_idx
].num_jump_labels
; j
++) {
375 uint32_t jump_label
= jump_labels
[jump_label_idx
].jump_labels
[j
];
376 char *name
= getpm4(jump_label
);
378 printlbl("%s", name
);
380 printlbl("UNKN%d", jump_label
);
387 printlbl("%s", fname
);
392 printlbl(" %s", lname
);
400 printf("\t%04x: %08x ", i
, instrs
[i
]);
407 /* a6xx changed the default immediate, and apparently 0
410 const uint32_t nop
= gpuver
>= 6 ? 0x1000000 : 0x0;
411 if (instrs
[i
] != nop
) {
412 printerr("[%08x]", instrs
[i
]);
418 print_gpu_reg(instrs
[i
]);
446 print_alu_name(opc
, instrs
[i
]);
447 print_dst(instr
->alui
.dst
);
450 print_src(instr
->alui
.src
);
453 printf("0x%04x", instr
->alui
.uimm
);
454 print_gpu_reg(instr
->alui
.uimm
);
456 /* print out unexpected bits: */
458 if (instr
->alui
.src
&& !src1
)
459 printerr(" (src=%02x)", instr
->alui
.src
);
468 print_dst(instr
->movi
.dst
);
469 printf(", 0x%04x", instr
->movi
.uimm
);
470 if (instr
->movi
.shift
)
471 printf(" << %u", instr
->movi
.shift
);
473 /* using mov w/ << 16 is popular way to construct a pkt7
474 * header to send (for ex, from PFP to ME), so check that
477 if ((instr
->movi
.shift
== 16) &&
478 ((instr
->movi
.uimm
& 0xff00) == 0x7000)) {
481 opc
= instr
->movi
.uimm
& 0x7f;
482 p
= _odd_parity_bit(opc
);
484 /* So, you'd think that checking the parity bit would be
485 * a good way to rule out false positives, but seems like
486 * ME doesn't really care.. at least it would filter out
487 * things that look like actual legit packets between
490 if (1 || p
== ((instr
->movi
.uimm
>> 7) & 0x1)) {
491 const char *name
= getpm4(opc
);
494 printlbl("%s", name
);
496 printlbl("UNKN%u", opc
);
501 print_gpu_reg(instr
->movi
.uimm
<< instr
->movi
.shift
);
508 if (instr
->alu
.alu
== OPC_NOT
|| instr
->alu
.alu
== OPC_MSB
)
512 printf("[%08x] ; ", instrs
[i
]);
517 /* special case mnemonics:
518 * reading $00 seems to always yield zero, and so:
519 * or $dst, $00, $src -> mov $dst, $src
520 * Maybe add one for negate too, ie.
521 * sub $dst, $00, $src ???
523 if ((instr
->alu
.alu
== OPC_OR
) && !instr
->alu
.src1
) {
527 print_alu_name(instr
->alu
.alu
, instrs
[i
]);
530 print_dst(instr
->alu
.dst
);
533 print_src(instr
->alu
.src1
);
536 print_src(instr
->alu
.src2
);
538 /* print out unexpected bits: */
541 printerr(" (pad=%03x)", instr
->alu
.pad
);
542 if (instr
->alu
.src1
&& !src1
)
543 printerr(" (src1=%02x)", instr
->alu
.src1
);
554 bool is_control_reg
= true;
564 is_control_reg
= false;
568 is_control_reg
= false;
572 assert(!"unreachable");
583 fprintf(stderr
, "A6xx control opcode on A5xx?\n");
588 print_src(instr
->control
.src1
);
590 print_src(instr
->control
.src2
);
592 if (is_control_reg
&& instr
->control
.flags
!= 0x4)
593 print_control_reg(instr
->control
.uimm
);
595 printf("0x%03x", instr
->control
.uimm
);
596 printf("], 0x%x", instr
->control
.flags
);
603 unsigned off
= i
+ instr
->br
.ioff
;
607 /* Since $00 reads back zero, it can be used as src for
608 * unconditional branches. (This only really makes sense
609 * for the BREQB.. or possible BRNEI if imm==0.)
611 * If bit=0 then branch is taken if *all* bits are zero.
612 * Otherwise it is taken if bit (bit-1) is clear.
614 * Note the instruction after a jump/branch is executed
615 * regardless of whether branch is taken, so use nop or
616 * take that into account in code.
618 if (instr
->br
.src
|| (opc
!= OPC_BRNEB
)) {
621 if (opc
== OPC_BRNEI
) {
624 } else if (opc
== OPC_BREQI
) {
627 } else if (opc
== OPC_BRNEB
) {
629 } else if (opc
== OPC_BREQB
) {
632 print_src(instr
->br
.src
);
634 printf(", 0x%x,", instr
->br
.bit_or_imm
);
636 printf(", b%u,", instr
->br
.bit_or_imm
);
640 if (verbose
&& instr
->br
.bit_or_imm
) {
641 printerr(" (src=%03x, bit=%03x) ",
642 instr
->br
.src
, instr
->br
.bit_or_imm
);
647 printlbl("%s", label_name(off
, true));
649 printf(" (#%d, %04x)", instr
->br
.ioff
, off
);
655 printlbl("%s", fxn_name(instr
->call
.uoff
));
657 printf(" (%04x)", instr
->call
.uoff
);
658 if (instr
->br
.bit_or_imm
|| instr
->br
.src
) {
659 printerr(" (src=%03x, bit=%03x) ",
660 instr
->br
.src
, instr
->br
.bit_or_imm
);
667 printf("[%08x] ; ", instrs
[i
]);
672 if (instr
->waitin
.pad
)
673 printf("[%08x] ; ", instrs
[i
]);
675 if (verbose
&& instr
->waitin
.pad
)
676 printerr(" (pad=%x)", instr
->waitin
.pad
);
678 case OPC_PREEMPTLEAVE6
:
680 printf("[%08x] ; op38", instrs
[i
]);
682 printf("preemptleave #");
683 printlbl("%s", label_name(instr
->call
.uoff
, true));
686 printerr("[%08x]", instrs
[i
]);
687 printf(" ; op%02x ", opc
);
688 print_dst(instr
->alui
.dst
);
690 print_src(instr
->alui
.src
);
691 print_gpu_reg(instrs
[i
] & 0xffff);
697 /* print jumptable: */
699 printf(";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n");
700 printf("; JUMP TABLE\n");
701 for (i
= 0; i
< 0x7f; i
++) {
702 int n
= i
;// + CP_NOP;
703 uint32_t offset
= jmptbl
[i
];
704 char *name
= getpm4(n
);
705 printf("%3d %02x: ", n
, n
);
706 printf("%04x", offset
);
708 printf(" ; %s", name
);
710 printf(" ; UNKN%d", n
);
717 #define CHUNKSIZE 4096
719 static char * readfile(const char *path
, int *sz
)
724 fd
= open(path
, O_RDONLY
);
729 buf
= realloc(buf
, n
+ CHUNKSIZE
);
730 ret
= read(fd
, buf
+ n
, CHUNKSIZE
);
735 } else if (ret
< CHUNKSIZE
) {
745 static void usage(void)
747 fprintf(stderr
, "Usage:\n"
748 "\tdisasm [-g GPUVER] [-v] [-c] filename.asm\n"
749 "\t\t-g - specify GPU version (5, etc)\n"
750 "\t\t-c - use colors\n"
751 "\t\t-v - verbose output\n"
756 int main(int argc
, char **argv
)
759 char *file
, *control_reg_name
;
763 /* Argument parsing: */
764 while ((c
= getopt (argc
, argv
, "g:vc")) != -1) {
767 gpuver
= atoi(optarg
);
780 if (optind
>= argc
) {
781 fprintf(stderr
, "no file specified!\n");
787 /* if gpu version not specified, infer from filename: */
789 if (strstr(file
, "a5")) {
791 } else if (strstr(file
, "a6")) {
798 printf("; a6xx microcode\n");
800 control_reg_name
= "A6XX_CONTROL_REG";
803 printf("; a5xx microcode\n");
805 control_reg_name
= "A5XX_CONTROL_REG";
808 fprintf(stderr
, "unknown GPU version!\n");
815 ctx
= rnndec_newcontext(db
);
816 ctx
->colors
= colors
? &envy_def_colors
: &envy_null_colors
;
818 rnn_parsefile(db
, "adreno.xml");
821 errx(db
->estatus
, "failed to parse register database");
822 dom
[0] = rnn_finddomain(db
, variant
);
823 dom
[1] = rnn_finddomain(db
, "AXXX");
824 control_regs
= rnn_finddomain(db
, control_reg_name
);
826 buf
= (uint32_t *)readfile(file
, &sz
);
828 printf("; Disassembling microcode: %s\n", file
);
829 printf("; Version: %08x\n\n", buf
[1]);
830 disasm(&buf
[1], sz
/4 - 1);