1 /*
2 * Copyright © 2017 Rob Clark <[email protected]>
3 * SPDX-License-Identifier: MIT
4 */
5
6 #include <assert.h>
7 #include <err.h>
8 #include <fcntl.h>
9 #include <getopt.h>
10 #include <stdarg.h>
11 #include <stdbool.h>
12 #include <stdint.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <unistd.h>
17
18 #include "util/os_file.h"
19
20 #include "compiler/isaspec/isaspec.h"
21
22 #include "freedreno_pm4.h"
23
24 #include "afuc.h"
25 #include "afuc-isa.h"
26 #include "util.h"
27 #include "emu.h"
28
29 int gpuver;
30
31 /* non-verbose mode should output something suitable to feed back into
32 * assembler.. verbose mode has additional output useful for debugging
33 * (like unexpected bits that are set)
34 */
35 static bool verbose = false;
36
37 /* emulator mode: */
38 static bool emulator = false;
39
40 #define printerr(fmt, ...) afuc_printc(AFUC_ERR, fmt, ##__VA_ARGS__)
41 #define printlbl(fmt, ...) afuc_printc(AFUC_LBL, fmt, ##__VA_ARGS__)
42
43 static const char *
getpm4(uint32_t id)44 getpm4(uint32_t id)
45 {
46 return afuc_pm_id_name(id);
47 }
48
49 static void
print_gpu_reg(FILE * out,uint32_t regbase)50 print_gpu_reg(FILE *out, uint32_t regbase)
51 {
52 if (regbase < 0x100)
53 return;
54
55 char *name = afuc_gpu_reg_name(regbase);
56 if (name) {
57 fprintf(out, "\t; %s", name);
58 free(name);
59 }
60 }
61
62 void
print_control_reg(uint32_t id)63 print_control_reg(uint32_t id)
64 {
65 char *name = afuc_control_reg_name(id);
66 if (name) {
67 printf("@%s", name);
68 free(name);
69 } else {
70 printf("0x%03x", id);
71 }
72 }
73
74 void
print_sqe_reg(uint32_t id)75 print_sqe_reg(uint32_t id)
76 {
77 char *name = afuc_sqe_reg_name(id);
78 if (name) {
79 printf("@%s", name);
80 free(name);
81 } else {
82 printf("0x%03x", id);
83 }
84 }
85
86 void
print_pipe_reg(uint32_t id)87 print_pipe_reg(uint32_t id)
88 {
89 char *name = afuc_pipe_reg_name(id);
90 if (name) {
91 printf("|%s", name);
92 free(name);
93 } else {
94 printf("0x%03x", id);
95 }
96 }
97
98 struct decode_state {
99 uint32_t immed;
100 uint8_t shift;
101 bool has_immed;
102 bool dst_is_addr;
103 };
104
105 static void
field_print_cb(struct isa_print_state * state,const char * field_name,uint64_t val)106 field_print_cb(struct isa_print_state *state, const char *field_name, uint64_t val)
107 {
108 if (!strcmp(field_name, "CONTROLREG")) {
109 char *name = afuc_control_reg_name(val);
110 if (name) {
111 isa_print(state, "@%s", name);
112 free(name);
113 } else {
114 isa_print(state, "0x%03x", (unsigned)val);
115 }
116 } else if (!strcmp(field_name, "SQEREG")) {
117 char *name = afuc_sqe_reg_name(val);
118 if (name) {
119 isa_print(state, "%%%s", name);
120 free(name);
121 } else {
122 isa_print(state, "0x%03x", (unsigned)val);
123 }
124 }
125 }
126
127 static void
pre_instr_cb(void * data,unsigned n,void * instr)128 pre_instr_cb(void *data, unsigned n, void *instr)
129 {
130 struct decode_state *state = data;
131 state->has_immed = state->dst_is_addr = false;
132 state->shift = 0;
133
134 if (verbose)
135 printf("\t%04x: %08x ", n, *(uint32_t *)instr);
136 }
137
138 static void
field_cb(void * data,const char * field_name,struct isa_decode_value * val)139 field_cb(void *data, const char *field_name, struct isa_decode_value *val)
140 {
141 struct decode_state *state = data;
142
143 if (!strcmp(field_name, "RIMMED")) {
144 state->immed = val->num;
145 state->has_immed = true;
146 }
147
148 if (!strcmp(field_name, "SHIFT")) {
149 state->shift = val->num;
150 }
151
152 if (!strcmp(field_name, "DST")) {
153 if (val->num == REG_ADDR)
154 state->dst_is_addr = true;
155 }
156 }
157
158 static void
post_instr_cb(void * data,unsigned n,void * instr)159 post_instr_cb(void *data, unsigned n, void *instr)
160 {
161 struct decode_state *state = data;
162
163 if (state->has_immed) {
164 uint32_t immed = state->immed << state->shift;
165 if (state->dst_is_addr && state->shift >= 16) {
166 immed &= ~0x40000; /* b18 disables auto-increment of address */
167 if ((immed & 0x00ffffff) == 0) {
168 printf("\t; ");
169 print_pipe_reg(immed >> 24);
170 }
171 } else {
172 print_gpu_reg(stdout, immed);
173 }
174 }
175 }
176
177 uint32_t jumptbl_offset = ~0;
178
179 /* Assume that instructions that don't match are raw data */
180 static void
no_match(FILE * out,const BITSET_WORD * bitset,size_t size)181 no_match(FILE *out, const BITSET_WORD *bitset, size_t size)
182 {
183 if (jumptbl_offset != ~0 && bitset[0] == afuc_nop_literal(jumptbl_offset, gpuver)) {
184 fprintf(out, "[#jumptbl]\n");
185 } else {
186 fprintf(out, "[%08x]", bitset[0]);
187 print_gpu_reg(out, bitset[0]);
188 fprintf(out, "\n");
189 }
190 }
191
192 static void
get_decode_options(struct isa_decode_options * options)193 get_decode_options(struct isa_decode_options *options)
194 {
195 *options = (struct isa_decode_options) {
196 .gpu_id = gpuver,
197 .branch_labels = true,
198 .field_cb = field_cb,
199 .field_print_cb = field_print_cb,
200 .pre_instr_cb = pre_instr_cb,
201 .post_instr_cb = post_instr_cb,
202 .no_match_cb = no_match,
203 };
204 }
205
206 static void
disasm_instr(struct isa_decode_options * options,uint32_t * instrs,unsigned pc)207 disasm_instr(struct isa_decode_options *options, uint32_t *instrs, unsigned pc)
208 {
209 afuc_isa_disasm(&instrs[pc], 4, stdout, options);
210 }
211
212 static void
setup_packet_table(struct isa_decode_options * options,uint32_t * jmptbl,uint32_t sizedwords)213 setup_packet_table(struct isa_decode_options *options,
214 uint32_t *jmptbl, uint32_t sizedwords)
215 {
216 struct isa_entrypoint *entrypoints = malloc(sizedwords * sizeof(struct isa_entrypoint));
217
218 for (unsigned i = 0; i < sizedwords; i++) {
219 entrypoints[i].offset = jmptbl[i];
220 unsigned n = i; // + CP_NOP;
221 entrypoints[i].name = afuc_pm_id_name(n);
222 if (!entrypoints[i].name) {
223 char *name;
224 asprintf(&name, "UNKN%d", n);
225 entrypoints[i].name = name;
226 }
227 }
228
229 options->entrypoints = entrypoints;
230 options->entrypoint_count = sizedwords;
231 }
232
233 static uint32_t
find_jump_table(uint32_t * instrs,uint32_t sizedwords,uint32_t * jmptbl,uint32_t jmptbl_size)234 find_jump_table(uint32_t *instrs, uint32_t sizedwords,
235 uint32_t *jmptbl, uint32_t jmptbl_size)
236 {
237 for (unsigned i = 0; i <= sizedwords - jmptbl_size; i++) {
238 bool found = true;
239 for (unsigned j = 0; j < jmptbl_size; j++) {
240 if (instrs[i + j] != jmptbl[j]) {
241 found = false;
242 break;
243 }
244 }
245 if (found)
246 return i;
247 }
248
249 return ~0;
250 }
251
252 static void
disasm(struct emu * emu)253 disasm(struct emu *emu)
254 {
255 uint32_t sizedwords = emu->sizedwords;
256 uint32_t lpac_offset = 0, bv_offset = 0;
257
258 EMU_GPU_REG(CP_SQE_INSTR_BASE);
259 EMU_GPU_REG(CP_LPAC_SQE_INSTR_BASE);
260 EMU_CONTROL_REG(BV_INSTR_BASE);
261 EMU_CONTROL_REG(LPAC_INSTR_BASE);
262
263 emu_init(emu);
264 emu->processor = EMU_PROC_SQE;
265
266 struct isa_decode_options options;
267 struct decode_state state;
268 get_decode_options(&options);
269 options.cbdata = &state;
270
271 #ifdef BOOTSTRAP_DEBUG
272 while (true) {
273 disasm_instr(&options, emu->instrs, emu->gpr_regs.pc);
274 emu_step(emu);
275 }
276 #endif
277
278 emu_run_bootstrap(emu);
279
280 /* Figure out if we have BV/LPAC SQE appended: */
281 if (gpuver >= 7) {
282 bv_offset = emu_get_reg64(emu, &BV_INSTR_BASE) -
283 emu_get_reg64(emu, &CP_SQE_INSTR_BASE);
284 bv_offset /= 4;
285 lpac_offset = emu_get_reg64(emu, &LPAC_INSTR_BASE) -
286 emu_get_reg64(emu, &CP_SQE_INSTR_BASE);
287 lpac_offset /= 4;
288 sizedwords = MIN2(bv_offset, lpac_offset);
289 } else {
290 if (emu_get_reg64(emu, &CP_LPAC_SQE_INSTR_BASE)) {
291 lpac_offset = emu_get_reg64(emu, &CP_LPAC_SQE_INSTR_BASE) -
292 emu_get_reg64(emu, &CP_SQE_INSTR_BASE);
293 lpac_offset /= 4;
294 sizedwords = lpac_offset;
295 }
296 }
297
298 setup_packet_table(&options, emu->jmptbl, ARRAY_SIZE(emu->jmptbl));
299
300 jumptbl_offset = find_jump_table(emu->instrs, sizedwords, emu->jmptbl,
301 ARRAY_SIZE(emu->jmptbl));
302
303 /* TODO add option to emulate LPAC SQE instead: */
304 if (emulator) {
305 /* Start from clean slate: */
306 emu_fini(emu);
307 emu_init(emu);
308
309 while (true) {
310 disasm_instr(&options, emu->instrs, emu->gpr_regs.pc);
311 emu_step(emu);
312 }
313 }
314
315 /* print instructions: */
316 afuc_isa_disasm(emu->instrs, MIN2(sizedwords, jumptbl_offset) * 4, stdout, &options);
317
318 /* print jump table */
319 if (jumptbl_offset != ~0) {
320 if (gpuver >= 7) {
321 /* The BV/LPAC microcode must be aligned to 32 bytes. On a7xx, by
322 * convention the firmware aligns the jumptable preceding it instead
323 * of the microcode itself, with nop instructions. Insert this
324 * directive to make sure that it stays aligned when reassembling
325 * even if the user modifies the BR microcode.
326 */
327 printf(".align 32\n");
328 }
329 printf("jumptbl:\n");
330 printf(".jumptbl\n");
331
332 if (jumptbl_offset + ARRAY_SIZE(emu->jmptbl) != sizedwords) {
333 for (unsigned i = jumptbl_offset + ARRAY_SIZE(emu->jmptbl); i < sizedwords; i++)
334 printf("[%08x]\n", emu->instrs[i]);
335 }
336 }
337
338 if (bv_offset) {
339 printf("\n.section BV\n");
340 printf(";\n");
341 printf("; BV microcode:\n");
342 printf(";\n");
343
344 emu_fini(emu);
345
346 emu->processor = EMU_PROC_BV;
347 emu->instrs += bv_offset;
348 emu->sizedwords -= bv_offset;
349
350 emu_init(emu);
351 emu_run_bootstrap(emu);
352
353 setup_packet_table(&options, emu->jmptbl, ARRAY_SIZE(emu->jmptbl));
354
355 uint32_t sizedwords = lpac_offset - bv_offset;
356
357 jumptbl_offset = find_jump_table(emu->instrs, sizedwords, emu->jmptbl,
358 ARRAY_SIZE(emu->jmptbl));
359
360 afuc_isa_disasm(emu->instrs, MIN2(sizedwords, jumptbl_offset) * 4, stdout, &options);
361
362 if (jumptbl_offset != ~0) {
363 printf(".align 32\n");
364 printf("jumptbl:\n");
365 printf(".jumptbl\n");
366 if (jumptbl_offset + ARRAY_SIZE(emu->jmptbl) != sizedwords) {
367 for (unsigned i = jumptbl_offset + ARRAY_SIZE(emu->jmptbl); i < sizedwords; i++)
368 printf("[%08x]\n", emu->instrs[i]);
369 }
370 }
371
372 emu->instrs -= bv_offset;
373 emu->sizedwords += bv_offset;
374 }
375
376 if (lpac_offset) {
377 printf("\n.section LPAC\n");
378 printf(";\n");
379 printf("; LPAC microcode:\n");
380 printf(";\n");
381
382 emu_fini(emu);
383
384 emu->processor = EMU_PROC_LPAC;
385 emu->instrs += lpac_offset;
386 emu->sizedwords -= lpac_offset;
387
388 emu_init(emu);
389 emu_run_bootstrap(emu);
390
391 setup_packet_table(&options, emu->jmptbl, ARRAY_SIZE(emu->jmptbl));
392
393 jumptbl_offset = find_jump_table(emu->instrs, emu->sizedwords, emu->jmptbl,
394 ARRAY_SIZE(emu->jmptbl));
395
396 afuc_isa_disasm(emu->instrs, MIN2(emu->sizedwords, jumptbl_offset) * 4, stdout, &options);
397
398 if (jumptbl_offset != ~0) {
399 printf("jumptbl:\n");
400 printf(".jumptbl\n");
401 if (jumptbl_offset + ARRAY_SIZE(emu->jmptbl) != emu->sizedwords) {
402 for (unsigned i = jumptbl_offset + ARRAY_SIZE(emu->jmptbl); i < emu->sizedwords; i++)
403 printf("[%08x]\n", emu->instrs[i]);
404 }
405 }
406
407 emu->instrs -= lpac_offset;
408 emu->sizedwords += lpac_offset;
409 }
410 }
411
412 static void
disasm_raw(uint32_t * instrs,int sizedwords)413 disasm_raw(uint32_t *instrs, int sizedwords)
414 {
415 struct isa_decode_options options;
416 struct decode_state state;
417 get_decode_options(&options);
418 options.cbdata = &state;
419
420 afuc_isa_disasm(instrs, sizedwords * 4, stdout, &options);
421 }
422
423 static void
disasm_legacy(uint32_t * buf,int sizedwords)424 disasm_legacy(uint32_t *buf, int sizedwords)
425 {
426 uint32_t *instrs = buf;
427 const int jmptbl_start = instrs[1] & 0xffff;
428 uint32_t *jmptbl = &buf[jmptbl_start];
429 int i;
430
431 struct isa_decode_options options;
432 struct decode_state state;
433 get_decode_options(&options);
434 options.cbdata = &state;
435
436 /* parse jumptable: */
437 setup_packet_table(&options, jmptbl, 0x80);
438
439 /* print instructions: */
440 afuc_isa_disasm(instrs, sizedwords * 4, stdout, &options);
441
442 /* print jumptable: */
443 if (verbose) {
444 printf(";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n");
445 printf("; JUMP TABLE\n");
446 for (i = 0; i < 0x7f; i++) {
447 int n = i; // + CP_NOP;
448 uint32_t offset = jmptbl[i];
449 const char *name = getpm4(n);
450 printf("%3d %02x: ", n, n);
451 printf("%04x", offset);
452 if (name) {
453 printf(" ; %s", name);
454 } else {
455 printf(" ; UNKN%d", n);
456 }
457 printf("\n");
458 }
459 }
460 }
461
462 static void
usage(void)463 usage(void)
464 {
465 fprintf(stderr, "Usage:\n"
466 "\tdisasm [-g GPUVER] [-v] [-c] [-r] filename.asm\n"
467 "\t\t-c - use colors\n"
468 "\t\t-e - emulator mode\n"
469 "\t\t-g - override GPU firmware id\n"
470 "\t\t-r - raw disasm, don't try to find jumptable\n"
471 "\t\t-v - verbose output\n"
472 );
473 exit(2);
474 }
475
476 int
main(int argc,char ** argv)477 main(int argc, char **argv)
478 {
479 uint32_t *buf;
480 char *file;
481 bool colors = false;
482 size_t sz;
483 int c, ret;
484 bool unit_test = false;
485 bool raw = false;
486 enum afuc_fwid fw_id = 0;
487
488 /* Argument parsing: */
489 while ((c = getopt(argc, argv, "ceg:rvu")) != -1) {
490 switch (c) {
491 case 'c':
492 colors = true;
493 break;
494 case 'e':
495 emulator = true;
496 verbose = true;
497 break;
498 case 'g':
499 fw_id = strtol(optarg, NULL, 16);
500 break;
501 case 'r':
502 raw = true;
503 break;
504 case 'v':
505 verbose = true;
506 break;
507 case 'u':
508 /* special "hidden" flag for unit tests, to avoid file paths (which
509 * can differ from reference output)
510 */
511 unit_test = true;
512 break;
513 default:
514 usage();
515 }
516 }
517
518 if (optind >= argc) {
519 fprintf(stderr, "no file specified!\n");
520 usage();
521 }
522
523 file = argv[optind];
524
525 buf = (uint32_t *)os_read_file(file, &sz);
526
527 if (!fw_id)
528 fw_id = afuc_get_fwid(buf[1]);
529
530 ret = afuc_util_init(fw_id, &gpuver, colors);
531 if (ret < 0) {
532 usage();
533 }
534
535 /* a6xx is *mostly* a superset of a5xx, but some opcodes shuffle
536 * around, and behavior of special regs is a bit different. Right
537 * now we only bother to support the a6xx variant.
538 */
539 if (emulator && (gpuver < 6 || gpuver > 7)) {
540 fprintf(stderr, "Emulator only supported on a6xx-a7xx!\n");
541 return 1;
542 }
543
544 printf("; a%dxx microcode\n", gpuver);
545
546 if (!unit_test)
547 printf("; Disassembling microcode: %s\n", file);
548 printf("; Version: %08x\n\n", buf[1]);
549
550 if (raw) {
551 disasm_raw(buf, sz / 4);
552 } else if (gpuver < 6) {
553 disasm_legacy(&buf[1], sz / 4 - 1);
554 } else {
555 struct emu emu = {
556 .instrs = &buf[1],
557 .sizedwords = sz / 4 - 1,
558 .fw_id = fw_id,
559 };
560
561 disasm(&emu);
562 }
563
564 return 0;
565 }
566