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