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