xref: /third_party/mesa3d/src/freedreno/afuc/disasm.c (revision bf215546)
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 "freedreno_pm4.h"
39
40#include "afuc.h"
41#include "util.h"
42#include "emu.h"
43
44static int gpuver;
45
46/* non-verbose mode should output something suitable to feed back into
47 * assembler.. verbose mode has additional output useful for debugging
48 * (like unexpected bits that are set)
49 */
50static bool verbose = false;
51
52/* emulator mode: */
53static bool emulator = false;
54
55static void
56print_gpu_reg(uint32_t regbase)
57{
58   if (regbase < 0x100)
59      return;
60
61   char *name = afuc_gpu_reg_name(regbase);
62   if (name) {
63      printf("\t; %s", name);
64      free(name);
65   }
66}
67
68#define printerr(fmt, ...) afuc_printc(AFUC_ERR, fmt, ##__VA_ARGS__)
69#define printlbl(fmt, ...) afuc_printc(AFUC_LBL, fmt, ##__VA_ARGS__)
70
71void
72print_src(unsigned reg)
73{
74   if (reg == REG_REM)
75      printf("$rem"); /* remainding dwords in packet */
76   else if (reg == REG_MEMDATA)
77      printf("$memdata");
78   else if (reg == REG_REGDATA)
79      printf("$regdata");
80   else if (reg == REG_DATA)
81      printf("$data");
82   else
83      printf("$%02x", reg);
84}
85
86void
87print_dst(unsigned reg)
88{
89   if (reg == REG_REM)
90      printf("$rem"); /* remainding dwords in packet */
91   else if (reg == REG_ADDR)
92      printf("$addr");
93   else if (reg == REG_USRADDR)
94      printf("$usraddr");
95   else if (reg == REG_DATA)
96      printf("$data");
97   else
98      printf("$%02x", reg);
99}
100
101static void
102print_alu_name(afuc_opc opc, uint32_t instr)
103{
104   if (opc == OPC_ADD) {
105      printf("add ");
106   } else if (opc == OPC_ADDHI) {
107      printf("addhi ");
108   } else if (opc == OPC_SUB) {
109      printf("sub ");
110   } else if (opc == OPC_SUBHI) {
111      printf("subhi ");
112   } else if (opc == OPC_AND) {
113      printf("and ");
114   } else if (opc == OPC_OR) {
115      printf("or ");
116   } else if (opc == OPC_XOR) {
117      printf("xor ");
118   } else if (opc == OPC_NOT) {
119      printf("not ");
120   } else if (opc == OPC_SHL) {
121      printf("shl ");
122   } else if (opc == OPC_USHR) {
123      printf("ushr ");
124   } else if (opc == OPC_ISHR) {
125      printf("ishr ");
126   } else if (opc == OPC_ROT) {
127      printf("rot ");
128   } else if (opc == OPC_MUL8) {
129      printf("mul8 ");
130   } else if (opc == OPC_MIN) {
131      printf("min ");
132   } else if (opc == OPC_MAX) {
133      printf("max ");
134   } else if (opc == OPC_CMP) {
135      printf("cmp ");
136   } else if (opc == OPC_MSB) {
137      printf("msb ");
138   } else {
139      printerr("[%08x]", instr);
140      printf("  ; alu%02x ", opc);
141   }
142}
143
144static const char *
145getpm4(uint32_t id)
146{
147   return afuc_pm_id_name(id);
148}
149
150static struct {
151   uint32_t offset;
152   uint32_t num_jump_labels;
153   uint32_t jump_labels[256];
154} jump_labels[1024];
155int num_jump_labels;
156
157static void
158add_jump_table_entry(uint32_t n, uint32_t offset)
159{
160   int i;
161
162   if (n > 128) /* can't possibly be a PM4 PKT3.. */
163      return;
164
165   for (i = 0; i < num_jump_labels; i++)
166      if (jump_labels[i].offset == offset)
167         goto add_label;
168
169   num_jump_labels = i + 1;
170   jump_labels[i].offset = offset;
171   jump_labels[i].num_jump_labels = 0;
172
173add_label:
174   jump_labels[i].jump_labels[jump_labels[i].num_jump_labels++] = n;
175   assert(jump_labels[i].num_jump_labels < 256);
176}
177
178static int
179get_jump_table_entry(uint32_t offset)
180{
181   int i;
182
183   for (i = 0; i < num_jump_labels; i++)
184      if (jump_labels[i].offset == offset)
185         return i;
186
187   return -1;
188}
189
190static uint32_t label_offsets[0x512];
191static int num_label_offsets;
192
193static int
194label_idx(uint32_t offset, bool create)
195{
196   int i;
197   for (i = 0; i < num_label_offsets; i++)
198      if (offset == label_offsets[i])
199         return i;
200   if (!create)
201      return -1;
202   label_offsets[i] = offset;
203   num_label_offsets = i + 1;
204   return i;
205}
206
207static const char *
208label_name(uint32_t offset, bool allow_jt)
209{
210   static char name[12];
211   int lidx;
212
213   if (allow_jt) {
214      lidx = get_jump_table_entry(offset);
215      if (lidx >= 0) {
216         int j;
217         for (j = 0; j < jump_labels[lidx].num_jump_labels; j++) {
218            uint32_t jump_label = jump_labels[lidx].jump_labels[j];
219            const char *str = getpm4(jump_label);
220            if (str)
221               return str;
222         }
223         // if we don't find anything w/ known name, maybe we should
224         // return UNKN%d to at least make it clear that this is some
225         // sort of jump-table entry?
226      }
227   }
228
229   lidx = label_idx(offset, false);
230   if (lidx < 0)
231      return NULL;
232   sprintf(name, "l%03d", lidx);
233   return name;
234}
235
236static uint32_t fxn_offsets[0x512];
237static int num_fxn_offsets;
238
239static int
240fxn_idx(uint32_t offset, bool create)
241{
242   int i;
243   for (i = 0; i < num_fxn_offsets; i++)
244      if (offset == fxn_offsets[i])
245         return i;
246   if (!create)
247      return -1;
248   fxn_offsets[i] = offset;
249   num_fxn_offsets = i + 1;
250   return i;
251}
252
253static const char *
254fxn_name(uint32_t offset)
255{
256   static char name[14];
257   int fidx = fxn_idx(offset, false);
258   if (fidx < 0)
259      return NULL;
260   sprintf(name, "fxn%02d", fidx);
261   return name;
262}
263
264void
265print_control_reg(uint32_t id)
266{
267   char *name = afuc_control_reg_name(id);
268   if (name) {
269      printf("@%s", name);
270      free(name);
271   } else {
272      printf("0x%03x", id);
273   }
274}
275
276void
277print_pipe_reg(uint32_t id)
278{
279   char *name = afuc_pipe_reg_name(id);
280   if (name) {
281      printf("|%s", name);
282      free(name);
283   } else {
284      printf("0x%03x", id);
285   }
286}
287
288static void
289disasm_instr(uint32_t *instrs, unsigned pc)
290{
291   int jump_label_idx;
292   afuc_instr *instr = (void *)&instrs[pc];
293   const char *fname, *lname;
294   afuc_opc opc;
295   bool rep;
296
297   afuc_get_opc(instr, &opc, &rep);
298
299   lname = label_name(pc, false);
300   fname = fxn_name(pc);
301   jump_label_idx = get_jump_table_entry(pc);
302
303   if (jump_label_idx >= 0) {
304      int j;
305      printf("\n");
306      for (j = 0; j < jump_labels[jump_label_idx].num_jump_labels; j++) {
307         uint32_t jump_label = jump_labels[jump_label_idx].jump_labels[j];
308         const char *name = getpm4(jump_label);
309         if (name) {
310            printlbl("%s", name);
311         } else {
312            printlbl("UNKN%d", jump_label);
313         }
314         printf(":\n");
315      }
316   }
317
318   if (fname) {
319      printlbl("%s", fname);
320      printf(":\n");
321   }
322
323   if (lname) {
324      printlbl(" %s", lname);
325      printf(":");
326   } else {
327      printf("      ");
328   }
329
330   if (verbose) {
331      printf("\t%04x: %08x  ", pc, instrs[pc]);
332   } else {
333      printf("  ");
334   }
335
336   switch (opc) {
337   case OPC_NOP: {
338      /* a6xx changed the default immediate, and apparently 0
339       * is illegal now.
340       */
341      const uint32_t nop = gpuver >= 6 ? 0x1000000 : 0x0;
342      if (instrs[pc] != nop) {
343         printerr("[%08x]", instrs[pc]);
344         printf("  ; ");
345      }
346      if (rep)
347         printf("(rep)");
348      printf("nop");
349      print_gpu_reg(instrs[pc]);
350
351      break;
352   }
353   case OPC_ADD:
354   case OPC_ADDHI:
355   case OPC_SUB:
356   case OPC_SUBHI:
357   case OPC_AND:
358   case OPC_OR:
359   case OPC_XOR:
360   case OPC_NOT:
361   case OPC_SHL:
362   case OPC_USHR:
363   case OPC_ISHR:
364   case OPC_ROT:
365   case OPC_MUL8:
366   case OPC_MIN:
367   case OPC_MAX:
368   case OPC_CMP: {
369      bool src1 = true;
370
371      if (opc == OPC_NOT)
372         src1 = false;
373
374      if (rep)
375         printf("(rep)");
376
377      print_alu_name(opc, instrs[pc]);
378      print_dst(instr->alui.dst);
379      printf(", ");
380      if (src1) {
381         print_src(instr->alui.src);
382         printf(", ");
383      }
384      printf("0x%04x", instr->alui.uimm);
385      print_gpu_reg(instr->alui.uimm);
386
387      /* print out unexpected bits: */
388      if (verbose) {
389         if (instr->alui.src && !src1)
390            printerr("  (src=%02x)", instr->alui.src);
391      }
392
393      break;
394   }
395   case OPC_MOVI: {
396      if (rep)
397         printf("(rep)");
398      printf("mov ");
399      print_dst(instr->movi.dst);
400      printf(", 0x%04x", instr->movi.uimm);
401      if (instr->movi.shift)
402         printf(" << %u", instr->movi.shift);
403
404      if ((instr->movi.dst == REG_ADDR) && (instr->movi.shift >= 16)) {
405         uint32_t val = (uint32_t)instr->movi.uimm << (uint32_t)instr->movi.shift;
406         val &= ~0x40000;  /* b18 seems to be a flag */
407
408         if ((val & 0x00ffffff) == 0) {
409            printf("\t; ");
410            print_pipe_reg(val >> 24);
411            break;
412         }
413      }
414      /* using mov w/ << 16 is popular way to construct a pkt7
415       * header to send (for ex, from PFP to ME), so check that
416       * case first
417       */
418      if ((instr->movi.shift == 16) &&
419          ((instr->movi.uimm & 0xff00) == 0x7000)) {
420         unsigned opc, p;
421
422         opc = instr->movi.uimm & 0x7f;
423         p = pm4_odd_parity_bit(opc);
424
425         /* So, you'd think that checking the parity bit would be
426          * a good way to rule out false positives, but seems like
427          * ME doesn't really care.. at least it would filter out
428          * things that look like actual legit packets between
429          * PFP and ME..
430          */
431         if (1 || p == ((instr->movi.uimm >> 7) & 0x1)) {
432            const char *name = getpm4(opc);
433            printf("\t; ");
434            if (name)
435               printlbl("%s", name);
436            else
437               printlbl("UNKN%u", opc);
438            break;
439         }
440      }
441
442      print_gpu_reg((uint32_t)instr->movi.uimm << (uint32_t)instr->movi.shift);
443
444      break;
445   }
446   case OPC_ALU: {
447      bool src1 = true;
448
449      if (instr->alu.alu == OPC_NOT || instr->alu.alu == OPC_MSB)
450         src1 = false;
451
452      if (instr->alu.pad)
453         printf("[%08x]  ; ", instrs[pc]);
454
455      if (rep)
456         printf("(rep)");
457      if (instr->alu.xmov)
458         printf("(xmov%d)", instr->alu.xmov);
459
460      /* special case mnemonics:
461       *   reading $00 seems to always yield zero, and so:
462       *      or $dst, $00, $src -> mov $dst, $src
463       *   Maybe add one for negate too, ie.
464       *      sub $dst, $00, $src ???
465       */
466      if ((instr->alu.alu == OPC_OR) && !instr->alu.src1) {
467         printf("mov ");
468         src1 = false;
469      } else {
470         print_alu_name(instr->alu.alu, instrs[pc]);
471      }
472
473      print_dst(instr->alu.dst);
474      if (src1) {
475         printf(", ");
476         print_src(instr->alu.src1);
477      }
478      printf(", ");
479      print_src(instr->alu.src2);
480
481      /* print out unexpected bits: */
482      if (verbose) {
483         if (instr->alu.pad)
484            printerr("  (pad=%01x)", instr->alu.pad);
485         if (instr->alu.src1 && !src1)
486            printerr("  (src1=%02x)", instr->alu.src1);
487      }
488
489      /* xmov is a modifier that makes the processor execute up to 3
490       * extra mov's after the current instruction. Given an ALU
491       * instruction:
492       *
493       * (xmovN) alu $dst, $src1, $src2
494       *
495       * In all of the uses in the firmware blob, $dst and $src2 are one
496       * of the "special" registers $data, $addr, $addr2. I've observed
497       * that if $dst isn't "special" then it's replaced with $00
498       * instead of $data, but I haven't checked what happens if $src2
499       * isn't "special".  Anyway, in the usual case, the HW produces a
500       * count M = min(N, $rem) and then does the following:
501       *
502       * M = 1:
503       * mov $data, $src2
504       *
505       * M = 2:
506       * mov $data, $src2
507       * mov $data, $src2
508       *
509       * M = 3:
510       * mov $data, $src2
511       * mov $dst, $src2 (special case for CP_CONTEXT_REG_BUNCH)
512       * mov $data, $src2
513       *
514       * It seems to be frequently used in combination with (rep) to
515       * provide a kind of hardware-based loop unrolling, and there's
516       * even a special case in the ISA to be able to do this with
517       * CP_CONTEXT_REG_BUNCH. However (rep) isn't required.
518       *
519       * This dumps the expected extra instructions, assuming that $rem
520       * isn't too small.
521       */
522      if (verbose && instr->alu.xmov) {
523         for (int i = 0; i < instr->alu.xmov; i++) {
524            printf("\n        ; mov ");
525            if (instr->alu.dst < 0x1d)
526               printf("$00");
527            else if (instr->alu.xmov == 3 && i == 1)
528               print_dst(instr->alu.dst);
529            else
530               printf("$data");
531            printf(", ");
532            print_src(instr->alu.src2);
533         }
534      }
535
536      break;
537   }
538   case OPC_CWRITE6:
539   case OPC_CREAD6:
540   case OPC_STORE6:
541   case OPC_LOAD6: {
542      if (rep)
543         printf("(rep)");
544
545      bool is_control_reg = true;
546      bool is_store = true;
547      if (gpuver >= 6) {
548         switch (opc) {
549         case OPC_CWRITE6:
550            printf("cwrite ");
551            break;
552         case OPC_CREAD6:
553            is_store = false;
554            printf("cread ");
555            break;
556         case OPC_STORE6:
557            is_control_reg = false;
558            printf("store ");
559            break;
560         case OPC_LOAD6:
561            is_control_reg = false;
562            is_store = false;
563            printf("load ");
564            break;
565         default:
566            assert(!"unreachable");
567         }
568      } else {
569         switch (opc) {
570         case OPC_CWRITE5:
571            printf("cwrite ");
572            break;
573         case OPC_CREAD5:
574            is_store = false;
575            printf("cread ");
576            break;
577         default:
578            fprintf(stderr, "A6xx control opcode on A5xx?\n");
579            exit(1);
580         }
581      }
582
583      if (is_store)
584         print_src(instr->control.src1);
585      else
586         print_dst(instr->control.src1);
587      printf(", [");
588      print_src(instr->control.src2);
589      printf(" + ");
590      if (is_control_reg && instr->control.flags != 0x4)
591         print_control_reg(instr->control.uimm);
592      else
593         printf("0x%03x", instr->control.uimm);
594      printf("], 0x%x", instr->control.flags);
595      break;
596   }
597   case OPC_BRNEI:
598   case OPC_BREQI:
599   case OPC_BRNEB:
600   case OPC_BREQB: {
601      unsigned off = pc + instr->br.ioff;
602
603      assert(!rep);
604
605      /* Since $00 reads back zero, it can be used as src for
606       * unconditional branches.  (This only really makes sense
607       * for the BREQB.. or possible BRNEI if imm==0.)
608       *
609       * If bit=0 then branch is taken if *all* bits are zero.
610       * Otherwise it is taken if bit (bit-1) is clear.
611       *
612       * Note the instruction after a jump/branch is executed
613       * regardless of whether branch is taken, so use nop or
614       * take that into account in code.
615       */
616      if (instr->br.src || (opc != OPC_BRNEB)) {
617         bool immed = false;
618
619         if (opc == OPC_BRNEI) {
620            printf("brne ");
621            immed = true;
622         } else if (opc == OPC_BREQI) {
623            printf("breq ");
624            immed = true;
625         } else if (opc == OPC_BRNEB) {
626            printf("brne ");
627         } else if (opc == OPC_BREQB) {
628            printf("breq ");
629         }
630         print_src(instr->br.src);
631         if (immed) {
632            printf(", 0x%x,", instr->br.bit_or_imm);
633         } else {
634            printf(", b%u,", instr->br.bit_or_imm);
635         }
636      } else {
637         printf("jump");
638         if (verbose && instr->br.bit_or_imm) {
639            printerr("  (src=%03x, bit=%03x) ", instr->br.src,
640                     instr->br.bit_or_imm);
641         }
642      }
643
644      printf(" #");
645      printlbl("%s", label_name(off, true));
646      if (verbose)
647         printf(" (#%d, %04x)", instr->br.ioff, off);
648      break;
649   }
650   case OPC_CALL:
651      assert(!rep);
652      printf("call #");
653      printlbl("%s", fxn_name(instr->call.uoff));
654      if (verbose) {
655         printf(" (%04x)", instr->call.uoff);
656         if (instr->br.bit_or_imm || instr->br.src) {
657            printerr("  (src=%03x, bit=%03x) ", instr->br.src,
658                     instr->br.bit_or_imm);
659         }
660      }
661      break;
662   case OPC_RET:
663      assert(!rep);
664      if (instr->ret.pad)
665         printf("[%08x]  ; ", instrs[pc]);
666      if (instr->ret.interrupt)
667         printf("iret");
668      else
669         printf("ret");
670      break;
671   case OPC_WIN:
672      assert(!rep);
673      if (instr->waitin.pad)
674         printf("[%08x]  ; ", instrs[pc]);
675      printf("waitin");
676      if (verbose && instr->waitin.pad)
677         printerr("  (pad=%x)", instr->waitin.pad);
678      break;
679   case OPC_PREEMPTLEAVE6:
680      if (gpuver < 6) {
681         printf("[%08x]  ; op38", instrs[pc]);
682      } else {
683         printf("preemptleave #");
684         printlbl("%s", label_name(instr->call.uoff, true));
685      }
686      break;
687   case OPC_SETSECURE:
688      /* Note: This seems to implicitly read the secure/not-secure state
689       * to set from the low bit of $02, and implicitly jumps to pc + 3
690       * (i.e. skipping the next two instructions) if it succeeds. We
691       * print these implicit parameters to make reading the disassembly
692       * easier.
693       */
694      if (instr->pad)
695         printf("[%08x]  ; ", instrs[pc]);
696      printf("setsecure $02, #");
697      printlbl("%s", label_name(pc + 3, true));
698      break;
699   default:
700      printerr("[%08x]", instrs[pc]);
701      printf("  ; op%02x ", opc);
702      print_dst(instr->alui.dst);
703      printf(", ");
704      print_src(instr->alui.src);
705      print_gpu_reg(instrs[pc] & 0xffff);
706      break;
707   }
708   printf("\n");
709}
710
711static void
712setup_packet_table(uint32_t *jmptbl, uint32_t sizedwords)
713{
714   num_jump_labels = 0;
715
716   for (unsigned i = 0; i < sizedwords; i++) {
717      unsigned offset = jmptbl[i];
718      unsigned n = i; // + CP_NOP;
719      add_jump_table_entry(n, offset);
720   }
721}
722
723static void
724setup_labels(uint32_t *instrs, uint32_t sizedwords)
725{
726   afuc_opc opc;
727   bool rep;
728
729   num_label_offsets = 0;
730
731   for (unsigned i = 0; i < sizedwords; i++) {
732      afuc_instr *instr = (void *)&instrs[i];
733
734      afuc_get_opc(instr, &opc, &rep);
735
736      switch (opc) {
737      case OPC_BRNEI:
738      case OPC_BREQI:
739      case OPC_BRNEB:
740      case OPC_BREQB:
741         label_idx(i + instr->br.ioff, true);
742         break;
743      case OPC_PREEMPTLEAVE6:
744         if (gpuver >= 6)
745            label_idx(instr->call.uoff, true);
746         break;
747      case OPC_CALL:
748         fxn_idx(instr->call.uoff, true);
749         break;
750      case OPC_SETSECURE:
751         /* this implicitly jumps to pc + 3 if successful */
752         label_idx(i + 3, true);
753         break;
754      default:
755         break;
756      }
757   }
758}
759
760static void
761disasm(struct emu *emu)
762{
763   uint32_t sizedwords = emu->sizedwords;
764   uint32_t lpac_offset = 0;
765
766   EMU_GPU_REG(CP_SQE_INSTR_BASE);
767   EMU_GPU_REG(CP_LPAC_SQE_INSTR_BASE);
768
769   emu_init(emu);
770
771#ifdef BOOTSTRAP_DEBUG
772   while (true) {
773      disasm_instr(emu->instrs, emu->gpr_regs.pc);
774      emu_step(emu);
775   }
776#endif
777
778   emu_run_bootstrap(emu);
779
780   /* Figure out if we have LPAC SQE appended: */
781   if (emu_get_reg64(emu, &CP_LPAC_SQE_INSTR_BASE)) {
782      lpac_offset = emu_get_reg64(emu, &CP_LPAC_SQE_INSTR_BASE) -
783            emu_get_reg64(emu, &CP_SQE_INSTR_BASE);
784      lpac_offset /= 4;
785      sizedwords = lpac_offset;
786   }
787
788   setup_packet_table(emu->jmptbl, ARRAY_SIZE(emu->jmptbl));
789   setup_labels(emu->instrs, emu->sizedwords);
790
791   /* TODO add option to emulate LPAC SQE instead: */
792   if (emulator) {
793      /* Start from clean slate: */
794      emu_fini(emu);
795      emu_init(emu);
796
797      while (true) {
798         disasm_instr(emu->instrs, emu->gpr_regs.pc);
799         emu_step(emu);
800      }
801   }
802
803   /* print instructions: */
804   for (int i = 0; i < sizedwords; i++) {
805      disasm_instr(emu->instrs, i);
806   }
807
808   if (!lpac_offset)
809      return;
810
811   printf(";\n");
812   printf("; LPAC microcode:\n");
813   printf(";\n");
814
815   emu_fini(emu);
816
817   emu->lpac = true;
818   emu->instrs += lpac_offset;
819   emu->sizedwords -= lpac_offset;
820
821   emu_init(emu);
822   emu_run_bootstrap(emu);
823
824   setup_packet_table(emu->jmptbl, ARRAY_SIZE(emu->jmptbl));
825   setup_labels(emu->instrs, emu->sizedwords);
826
827   /* print instructions: */
828   for (int i = 0; i < emu->sizedwords; i++) {
829      disasm_instr(emu->instrs, i);
830   }
831}
832
833
834static void
835disasm_legacy(uint32_t *buf, int sizedwords)
836{
837   uint32_t *instrs = buf;
838   const int jmptbl_start = instrs[1] & 0xffff;
839   uint32_t *jmptbl = &buf[jmptbl_start];
840   int i;
841
842   /* parse jumptable: */
843   setup_packet_table(jmptbl, 0x80);
844
845   /* do a pre-pass to find instructions that are potential branch targets,
846    * and add labels for them:
847    */
848   setup_labels(instrs, jmptbl_start);
849
850   /* print instructions: */
851   for (i = 0; i < jmptbl_start; i++) {
852      disasm_instr(instrs, i);
853   }
854
855   /* print jumptable: */
856   if (verbose) {
857      printf(";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n");
858      printf("; JUMP TABLE\n");
859      for (i = 0; i < 0x7f; i++) {
860         int n = i; // + CP_NOP;
861         uint32_t offset = jmptbl[i];
862         const char *name = getpm4(n);
863         printf("%3d %02x: ", n, n);
864         printf("%04x", offset);
865         if (name) {
866            printf("   ; %s", name);
867         } else {
868            printf("   ; UNKN%d", n);
869         }
870         printf("\n");
871      }
872   }
873}
874
875static void
876usage(void)
877{
878   fprintf(stderr, "Usage:\n"
879                   "\tdisasm [-g GPUVER] [-v] [-c] filename.asm\n"
880                   "\t\t-g - specify GPU version (5, etc)\n"
881                   "\t\t-c - use colors\n"
882                   "\t\t-v - verbose output\n"
883                   "\t\t-e - emulator mode\n");
884   exit(2);
885}
886
887int
888main(int argc, char **argv)
889{
890   uint32_t *buf;
891   char *file;
892   bool colors = false;
893   uint32_t gpu_id = 0;
894   size_t sz;
895   int c, ret;
896   bool unit_test = false;
897
898   /* Argument parsing: */
899   while ((c = getopt(argc, argv, "g:vceu")) != -1) {
900      switch (c) {
901      case 'g':
902         gpu_id = atoi(optarg);
903         break;
904      case 'v':
905         verbose = true;
906         break;
907      case 'c':
908         colors = true;
909         break;
910      case 'e':
911         emulator = true;
912         verbose  = true;
913         break;
914      case 'u':
915         unit_test = true;
916         break;
917      default:
918         usage();
919      }
920   }
921
922   if (optind >= argc) {
923      fprintf(stderr, "no file specified!\n");
924      usage();
925   }
926
927   file = argv[optind];
928
929   /* if gpu version not specified, infer from filename: */
930   if (!gpu_id) {
931      char *str = strstr(file, "a5");
932      if (!str)
933         str = strstr(file, "a6");
934      if (str)
935         gpu_id = atoi(str + 1);
936   }
937
938   if (gpu_id < 500) {
939      printf("invalid gpu_id: %d\n", gpu_id);
940      return -1;
941   }
942
943   gpuver = gpu_id / 100;
944
945   /* a6xx is *mostly* a superset of a5xx, but some opcodes shuffle
946    * around, and behavior of special regs is a bit different.  Right
947    * now we only bother to support the a6xx variant.
948    */
949   if (emulator && (gpuver != 6)) {
950      fprintf(stderr, "Emulator only supported on a6xx!\n");
951      return 1;
952   }
953
954   ret = afuc_util_init(gpuver, colors);
955   if (ret < 0) {
956      usage();
957   }
958
959   printf("; a%dxx microcode\n", gpuver);
960
961   buf = (uint32_t *)os_read_file(file, &sz);
962
963   if (!unit_test)
964      printf("; Disassembling microcode: %s\n", file);
965   printf("; Version: %08x\n\n", buf[1]);
966
967   if (gpuver < 6) {
968      disasm_legacy(&buf[1], sz / 4 - 1);
969   } else {
970      struct emu emu = {
971            .instrs = &buf[1],
972            .sizedwords = sz / 4 - 1,
973            .gpu_id = gpu_id,
974      };
975
976      disasm(&emu);
977   }
978
979   return 0;
980}
981