xref: /aosp_15_r20/external/mesa3d/src/freedreno/afuc/disasm.c (revision 6104692788411f58d303aa86923a9ff6ecaded22)
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