ea9f34cd97fb8e5d710e49f14e8b40ac1845ec9b
[mesa.git] / src / freedreno / afuc / disasm.c
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 <stdint.h>
25 #include <stdbool.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <stdarg.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <assert.h>
33 #include <getopt.h>
34
35 #include "afuc.h"
36 #include "rnn.h"
37 #include "rnndec.h"
38
39 static int gpuver;
40
41
42 static struct rnndeccontext *ctx;
43 static struct rnndb *db;
44 static struct rnndomain *control_regs;
45 struct rnndomain *dom[2];
46 const char *variant;
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 static void print_gpu_reg(uint32_t regbase)
55 {
56 struct rnndomain *d = NULL;
57
58 if (regbase < 0x100)
59 return;
60
61 if (rnndec_checkaddr(ctx, dom[0], regbase, 0))
62 d = dom[0];
63 else if (rnndec_checkaddr(ctx, dom[1], regbase, 0))
64 d = dom[1];
65
66 if (d) {
67 struct rnndecaddrinfo *info = rnndec_decodeaddr(ctx, d, regbase, 0);
68 if (info) {
69 printf("\t; %s", info->name);
70 free(info->name);
71 free(info);
72 return;
73 }
74 }
75 }
76
77 static void printc(const char *c, const char *fmt, ...)
78 {
79 va_list args;
80 printf("%s", c);
81 va_start(args, fmt);
82 vprintf(fmt, args);
83 va_end(args);
84 printf("%s", ctx->colors->reset);
85 }
86
87 #define printerr(fmt, ...) printc(ctx->colors->err, fmt, ##__VA_ARGS__)
88 #define printlbl(fmt, ...) printc(ctx->colors->btarg, fmt, ##__VA_ARGS__)
89
90 static void print_reg(unsigned reg)
91 {
92 // XXX seems like *reading* $00 --> literal zero??
93 // seems like read from $1c gives packet remaining len??
94 // $01 current packet header, writing to $01 triggers
95 // parsing header and jumping to appropriate handler.
96 if (reg == 0x1c)
97 printf("$rem"); /* remainding dwords in packet */
98 else if (reg == 0x1d)
99 printf("$addr");
100 else if (reg == 0x1e)
101 printf("$addr2"); // XXX
102 else if (reg == 0x1f)
103 printf("$data");
104 else
105 printf("$%02x", reg);
106 }
107
108 static void print_src(unsigned reg)
109 {
110 print_reg(reg);
111 }
112
113 static void print_dst(unsigned reg)
114 {
115 print_reg(reg);
116 }
117
118 static void print_alu_name(afuc_opc opc, uint32_t instr)
119 {
120 if (opc == OPC_ADD) {
121 printf("add ");
122 } else if (opc == OPC_ADDHI) {
123 printf("addhi ");
124 } else if (opc == OPC_SUB) {
125 printf("sub ");
126 } else if (opc == OPC_SUBHI) {
127 printf("subhi ");
128 } else if (opc == OPC_AND) {
129 printf("and ");
130 } else if (opc == OPC_OR) {
131 printf("or ");
132 } else if (opc == OPC_XOR) {
133 printf("xor ");
134 } else if (opc == OPC_NOT) {
135 printf("not ");
136 } else if (opc == OPC_SHL) {
137 printf("shl ");
138 } else if (opc == OPC_USHR) {
139 printf("ushr ");
140 } else if (opc == OPC_ISHR) {
141 printf("ishr ");
142 } else if (opc == OPC_ROT) {
143 printf("rot ");
144 } else if (opc == OPC_MUL8) {
145 printf("mul8 ");
146 } else if (opc == OPC_MIN) {
147 printf("min ");
148 } else if (opc == OPC_MAX) {
149 printf("max ");
150 } else if (opc == OPC_CMP) {
151 printf("cmp ");
152 } else if (opc == OPC_MSB) {
153 printf("msb ");
154 } else {
155 printerr("[%08x]", instr);
156 printf(" ; alu%02x ", opc);
157 }
158 }
159
160 static char *getpm4(uint32_t id)
161 {
162 struct rnnenum *en = rnn_findenum(ctx->db, "adreno_pm4_type3_packets");
163 if (en) {
164 int i;
165 for (i = 0; i < en->valsnum; i++)
166 if (en->vals[i]->valvalid && en->vals[i]->value == id) {
167 const char *v = en->vals[i]->varinfo.variantsstr;
168 if (v && !strstr(v, variant))
169 continue;
170 return en->vals[i]->name;
171 }
172 }
173 return NULL;
174 }
175
176 static inline unsigned
177 _odd_parity_bit(unsigned val)
178 {
179 /* See: http://graphics.stanford.edu/~seander/bithacks.html#ParityParallel
180 * note that we want odd parity so 0x6996 is inverted.
181 */
182 val ^= val >> 16;
183 val ^= val >> 8;
184 val ^= val >> 4;
185 val &= 0xf;
186 return (~0x6996 >> val) & 1;
187 }
188
189 static struct {
190 uint32_t offset;
191 uint32_t num_jump_labels;
192 uint32_t jump_labels[256];
193 } jump_labels[1024];
194 int num_jump_labels;
195
196 static void add_jump_table_entry(uint32_t n, uint32_t offset)
197 {
198 int i;
199
200 if (n > 128) /* can't possibly be a PM4 PKT3.. */
201 return;
202
203 for (i = 0; i < num_jump_labels; i++)
204 if (jump_labels[i].offset == offset)
205 goto add_label;
206
207 num_jump_labels = i + 1;
208 jump_labels[i].offset = offset;
209 jump_labels[i].num_jump_labels = 0;
210
211 add_label:
212 jump_labels[i].jump_labels[jump_labels[i].num_jump_labels++] = n;
213 assert(jump_labels[i].num_jump_labels < 256);
214 }
215
216 static int get_jump_table_entry(uint32_t offset)
217 {
218 int i;
219
220 for (i = 0; i < num_jump_labels; i++)
221 if (jump_labels[i].offset == offset)
222 return i;
223
224 return -1;
225 }
226
227 static uint32_t label_offsets[0x512];
228 static int num_label_offsets;
229
230 static int label_idx(uint32_t offset, bool create)
231 {
232 int i;
233 for (i = 0; i < num_label_offsets; i++)
234 if (offset == label_offsets[i])
235 return i;
236 if (!create)
237 return -1;
238 label_offsets[i] = offset;
239 num_label_offsets = i+1;
240 return i;
241 }
242
243 static const char *
244 label_name(uint32_t offset, bool allow_jt)
245 {
246 static char name[8];
247 int lidx;
248
249 if (allow_jt) {
250 lidx = get_jump_table_entry(offset);
251 if (lidx >= 0) {
252 int j;
253 for (j = 0; j < jump_labels[lidx].num_jump_labels; j++) {
254 uint32_t jump_label = jump_labels[lidx].jump_labels[j];
255 char *str = getpm4(jump_label);
256 if (str)
257 return str;
258 }
259 // if we don't find anything w/ known name, maybe we should
260 // return UNKN%d to at least make it clear that this is some
261 // sort of jump-table entry?
262 }
263 }
264
265 lidx = label_idx(offset, false);
266 if (lidx < 0)
267 return NULL;
268 sprintf(name, "l%03d", lidx);
269 return name;
270 }
271
272
273 static uint32_t fxn_offsets[0x512];
274 static int num_fxn_offsets;
275
276 static int fxn_idx(uint32_t offset, bool create)
277 {
278 int i;
279 for (i = 0; i < num_fxn_offsets; i++)
280 if (offset == fxn_offsets[i])
281 return i;
282 if (!create)
283 return -1;
284 fxn_offsets[i] = offset;
285 num_fxn_offsets = i+1;
286 return i;
287 }
288
289 static const char *
290 fxn_name(uint32_t offset)
291 {
292 static char name[8];
293 int fidx = fxn_idx(offset, false);
294 if (fidx < 0)
295 return NULL;
296 sprintf(name, "fxn%02d", fidx);
297 return name;
298 }
299
300 static void print_control_reg(uint32_t id)
301 {
302 if (rnndec_checkaddr(ctx, control_regs, id, 0)) {
303 struct rnndecaddrinfo *info = rnndec_decodeaddr(ctx, control_regs, id, 0);
304 printf("@%s", info->name);
305 free(info->name);
306 free(info);
307 } else {
308 printf("0x%03x", id);
309 }
310 }
311
312 static void disasm(uint32_t *buf, int sizedwords)
313 {
314 uint32_t *instrs = buf;
315 const int jmptbl_start = instrs[1] & 0xffff;
316 uint32_t *jmptbl = &buf[jmptbl_start];
317 afuc_opc opc;
318 bool rep;
319 int i;
320
321
322 /* parse jumptable: */
323 for (i = 0; i < 0x80; i++) {
324 unsigned offset = jmptbl[i];
325 unsigned n = i;// + CP_NOP;
326 add_jump_table_entry(n, offset);
327 }
328
329 /* do a pre-pass to find instructions that are potential branch targets,
330 * and add labels for them:
331 */
332 for (i = 0; i < jmptbl_start; i++) {
333 afuc_instr *instr = (void *)&instrs[i];
334
335 afuc_get_opc(instr, &opc, &rep);
336
337 switch (opc) {
338 case OPC_BRNEI:
339 case OPC_BREQI:
340 case OPC_BRNEB:
341 case OPC_BREQB:
342 label_idx(i + instr->br.ioff, true);
343 break;
344 case OPC_PREEMPTLEAVE6:
345 if (gpuver >= 6)
346 label_idx(instr->call.uoff, true);
347 break;
348 case OPC_CALL:
349 fxn_idx(instr->call.uoff, true);
350 break;
351 default:
352 break;
353 }
354 }
355
356 /* print instructions: */
357 for (i = 0; i < jmptbl_start; i++) {
358 int jump_label_idx;
359 afuc_instr *instr = (void *)&instrs[i];
360 const char *fname, *lname;
361 afuc_opc opc;
362 bool rep;
363
364 afuc_get_opc(instr, &opc, &rep);
365
366 lname = label_name(i, false);
367 fname = fxn_name(i);
368 jump_label_idx = get_jump_table_entry(i);
369
370 if (jump_label_idx >= 0) {
371 int j;
372 printf("\n");
373 for (j = 0; j < jump_labels[jump_label_idx].num_jump_labels; j++) {
374 uint32_t jump_label = jump_labels[jump_label_idx].jump_labels[j];
375 char *name = getpm4(jump_label);
376 if (name) {
377 printlbl("%s", name);
378 } else {
379 printlbl("UNKN%d", jump_label);
380 }
381 printf(":\n");
382 }
383 }
384
385 if (fname) {
386 printlbl("%s", fname);
387 printf(":\n");
388 }
389
390 if (lname) {
391 printlbl(" %s", lname);
392 printf(":");
393 } else {
394 printf(" ");
395 }
396
397
398 if (verbose) {
399 printf("\t%04x: %08x ", i, instrs[i]);
400 } else {
401 printf(" ");
402 }
403
404 switch (opc) {
405 case OPC_NOP: {
406 /* a6xx changed the default immediate, and apparently 0
407 * is illegal now.
408 */
409 const uint32_t nop = gpuver >= 6 ? 0x1000000 : 0x0;
410 if (instrs[i] != nop) {
411 printerr("[%08x]", instrs[i]);
412 printf(" ; ");
413 }
414 if (rep)
415 printf("(rep)");
416 printf("nop");
417 print_gpu_reg(instrs[i]);
418
419 break;
420 }
421 case OPC_ADD:
422 case OPC_ADDHI:
423 case OPC_SUB:
424 case OPC_SUBHI:
425 case OPC_AND:
426 case OPC_OR:
427 case OPC_XOR:
428 case OPC_NOT:
429 case OPC_SHL:
430 case OPC_USHR:
431 case OPC_ISHR:
432 case OPC_ROT:
433 case OPC_MUL8:
434 case OPC_MIN:
435 case OPC_MAX:
436 case OPC_CMP: {
437 bool src1 = true;
438
439 if (opc == OPC_NOT)
440 src1 = false;
441
442 if (rep)
443 printf("(rep)");
444
445 print_alu_name(opc, instrs[i]);
446 print_dst(instr->alui.dst);
447 printf(", ");
448 if (src1) {
449 print_src(instr->alui.src);
450 printf(", ");
451 }
452 printf("0x%04x", instr->alui.uimm);
453 print_gpu_reg(instr->alui.uimm);
454
455 /* print out unexpected bits: */
456 if (verbose) {
457 if (instr->alui.src && !src1)
458 printerr(" (src=%02x)", instr->alui.src);
459 }
460
461 break;
462 }
463 case OPC_MOVI: {
464 if (rep)
465 printf("(rep)");
466 printf("mov ");
467 print_dst(instr->movi.dst);
468 printf(", 0x%04x", instr->movi.uimm);
469 if (instr->movi.shift)
470 printf(" << %u", instr->movi.shift);
471
472 /* using mov w/ << 16 is popular way to construct a pkt7
473 * header to send (for ex, from PFP to ME), so check that
474 * case first
475 */
476 if ((instr->movi.shift == 16) &&
477 ((instr->movi.uimm & 0xff00) == 0x7000)) {
478 unsigned opc, p;
479
480 opc = instr->movi.uimm & 0x7f;
481 p = _odd_parity_bit(opc);
482
483 /* So, you'd think that checking the parity bit would be
484 * a good way to rule out false positives, but seems like
485 * ME doesn't really care.. at least it would filter out
486 * things that look like actual legit packets between
487 * PFP and ME..
488 */
489 if (1 || p == ((instr->movi.uimm >> 7) & 0x1)) {
490 const char *name = getpm4(opc);
491 printf("\t; ");
492 if (name)
493 printlbl("%s", name);
494 else
495 printlbl("UNKN%u", opc);
496 break;
497 }
498 }
499
500 print_gpu_reg(instr->movi.uimm << instr->movi.shift);
501
502 break;
503 }
504 case OPC_ALU: {
505 bool src1 = true;
506
507 if (instr->alu.alu == OPC_NOT || instr->alu.alu == OPC_MSB)
508 src1 = false;
509
510 if (instr->alu.pad)
511 printf("[%08x] ; ", instrs[i]);
512
513 if (rep)
514 printf("(rep)");
515
516 /* special case mnemonics:
517 * reading $00 seems to always yield zero, and so:
518 * or $dst, $00, $src -> mov $dst, $src
519 * Maybe add one for negate too, ie.
520 * sub $dst, $00, $src ???
521 */
522 if ((instr->alu.alu == OPC_OR) && !instr->alu.src1) {
523 printf("mov ");
524 src1 = false;
525 } else {
526 print_alu_name(instr->alu.alu, instrs[i]);
527 }
528
529 print_dst(instr->alu.dst);
530 if (src1) {
531 printf(", ");
532 print_src(instr->alu.src1);
533 }
534 printf(", ");
535 print_src(instr->alu.src2);
536
537 /* print out unexpected bits: */
538 if (verbose) {
539 if (instr->alu.pad)
540 printerr(" (pad=%03x)", instr->alu.pad);
541 if (instr->alu.src1 && !src1)
542 printerr(" (src1=%02x)", instr->alu.src1);
543 }
544 break;
545 }
546 case OPC_CWRITE6:
547 case OPC_CREAD6:
548 case OPC_STORE6:
549 case OPC_LOAD6: {
550 if (rep)
551 printf("(rep)");
552
553 bool is_control_reg = true;
554 if (gpuver >= 6) {
555 switch (opc) {
556 case OPC_CWRITE6:
557 printf("cwrite ");
558 break;
559 case OPC_CREAD6:
560 printf("cread ");
561 break;
562 case OPC_STORE6:
563 is_control_reg = false;
564 printf("store ");
565 break;
566 case OPC_LOAD6:
567 is_control_reg = false;
568 printf("load ");
569 break;
570 default:
571 assert(!"unreachable");
572 }
573 } else {
574 switch (opc) {
575 case OPC_CWRITE5:
576 printf("cwrite ");
577 break;
578 case OPC_CREAD5:
579 printf("cread ");
580 break;
581 default:
582 fprintf(stderr, "A6xx control opcode on A5xx?\n");
583 exit(1);
584 }
585 }
586
587 print_src(instr->control.src1);
588 printf(", [");
589 print_src(instr->control.src2);
590 printf(" + ");
591 if (is_control_reg && instr->control.flags != 0x4)
592 print_control_reg(instr->control.uimm);
593 else
594 printf("0x%03x", instr->control.uimm);
595 printf("], 0x%x", instr->control.flags);
596 break;
597 }
598 case OPC_BRNEI:
599 case OPC_BREQI:
600 case OPC_BRNEB:
601 case OPC_BREQB: {
602 unsigned off = i + instr->br.ioff;
603
604 assert(!rep);
605
606 /* Since $00 reads back zero, it can be used as src for
607 * unconditional branches. (This only really makes sense
608 * for the BREQB.. or possible BRNEI if imm==0.)
609 *
610 * If bit=0 then branch is taken if *all* bits are zero.
611 * Otherwise it is taken if bit (bit-1) is clear.
612 *
613 * Note the instruction after a jump/branch is executed
614 * regardless of whether branch is taken, so use nop or
615 * take that into account in code.
616 */
617 if (instr->br.src || (opc != OPC_BRNEB)) {
618 bool immed = false;
619
620 if (opc == OPC_BRNEI) {
621 printf("brne ");
622 immed = true;
623 } else if (opc == OPC_BREQI) {
624 printf("breq ");
625 immed = true;
626 } else if (opc == OPC_BRNEB) {
627 printf("brne ");
628 } else if (opc == OPC_BREQB) {
629 printf("breq ");
630 }
631 print_src(instr->br.src);
632 if (immed) {
633 printf(", 0x%x,", instr->br.bit_or_imm);
634 } else {
635 printf(", b%u,", instr->br.bit_or_imm);
636 }
637 } else {
638 printf("jump");
639 if (verbose && instr->br.bit_or_imm) {
640 printerr(" (src=%03x, bit=%03x) ",
641 instr->br.src, instr->br.bit_or_imm);
642 }
643 }
644
645 printf(" #");
646 printlbl("%s", label_name(off, true));
647 if (verbose)
648 printf(" (#%d, %04x)", instr->br.ioff, off);
649 break;
650 }
651 case OPC_CALL:
652 assert(!rep);
653 printf("call #");
654 printlbl("%s", fxn_name(instr->call.uoff));
655 if (verbose) {
656 printf(" (%04x)", instr->call.uoff);
657 if (instr->br.bit_or_imm || instr->br.src) {
658 printerr(" (src=%03x, bit=%03x) ",
659 instr->br.src, instr->br.bit_or_imm);
660 }
661 }
662 break;
663 case OPC_RET:
664 assert(!rep);
665 if (instr->pad)
666 printf("[%08x] ; ", instrs[i]);
667 printf("ret");
668 break;
669 case OPC_WIN:
670 assert(!rep);
671 if (instr->waitin.pad)
672 printf("[%08x] ; ", instrs[i]);
673 printf("waitin");
674 if (verbose && instr->waitin.pad)
675 printerr(" (pad=%x)", instr->waitin.pad);
676 break;
677 case OPC_PREEMPTLEAVE6:
678 if (gpuver < 6) {
679 printf("[%08x] ; op38", instrs[i]);
680 }
681 printf("preemptleave #");
682 printlbl("%s", label_name(instr->call.uoff, true));
683 break;
684 default:
685 printerr("[%08x]", instrs[i]);
686 printf(" ; op%02x ", opc);
687 print_dst(instr->alui.dst);
688 printf(", ");
689 print_src(instr->alui.src);
690 print_gpu_reg(instrs[i] & 0xffff);
691 break;
692 }
693 printf("\n");
694 }
695
696 /* print jumptable: */
697 if (verbose) {
698 printf(";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n");
699 printf("; JUMP TABLE\n");
700 for (i = 0; i < 0x7f; i++) {
701 int n = i;// + CP_NOP;
702 uint32_t offset = jmptbl[i];
703 char *name = getpm4(n);
704 printf("%3d %02x: ", n, n);
705 printf("%04x", offset);
706 if (name) {
707 printf(" ; %s", name);
708 } else {
709 printf(" ; UNKN%d", n);
710 }
711 printf("\n");
712 }
713 }
714 }
715
716 #define CHUNKSIZE 4096
717
718 static char * readfile(const char *path, int *sz)
719 {
720 char *buf = NULL;
721 int fd, ret, n = 0;
722
723 fd = open(path, O_RDONLY);
724 if (fd < 0)
725 return NULL;
726
727 while (1) {
728 buf = realloc(buf, n + CHUNKSIZE);
729 ret = read(fd, buf + n, CHUNKSIZE);
730 if (ret < 0) {
731 free(buf);
732 *sz = 0;
733 return NULL;
734 } else if (ret < CHUNKSIZE) {
735 n += ret;
736 *sz = n;
737 return buf;
738 } else {
739 n += CHUNKSIZE;
740 }
741 }
742 }
743
744 static void usage(void)
745 {
746 fprintf(stderr, "Usage:\n"
747 "\tdisasm [-g GPUVER] [-v] [-c] filename.asm\n"
748 "\t\t-g - specify GPU version (5, etc)\n"
749 "\t\t-c - use colors\n"
750 "\t\t-v - verbose output\n"
751 );
752 exit(2);
753 }
754
755 int main(int argc, char **argv)
756 {
757 uint32_t *buf;
758 char *file, *control_reg_name;
759 bool colors = false;
760 int sz, c;
761
762 /* Argument parsing: */
763 while ((c = getopt (argc, argv, "g:vc")) != -1) {
764 switch (c) {
765 case 'g':
766 gpuver = atoi(optarg);
767 break;
768 case 'v':
769 verbose = true;
770 break;
771 case 'c':
772 colors = true;
773 break;
774 default:
775 usage();
776 }
777 }
778
779 if (optind >= argc) {
780 fprintf(stderr, "no file specified!\n");
781 usage();
782 }
783
784 file = argv[optind];
785
786 /* if gpu version not specified, infer from filename: */
787 if (!gpuver) {
788 if (strstr(file, "a5")) {
789 gpuver = 5;
790 } else if (strstr(file, "a6")) {
791 gpuver = 6;
792 }
793 }
794
795 switch (gpuver) {
796 case 6:
797 printf("; a6xx microcode\n");
798 variant = "A6XX";
799 control_reg_name = "A6XX_CONTROL_REG";
800 break;
801 case 5:
802 printf("; a5xx microcode\n");
803 variant = "A5XX";
804 control_reg_name = "A5XX_CONTROL_REG";
805 break;
806 default:
807 fprintf(stderr, "unknown GPU version!\n");
808 usage();
809 }
810
811 rnn_init();
812 db = rnn_newdb();
813
814 ctx = rnndec_newcontext(db);
815 ctx->colors = colors ? &envy_def_colors : &envy_null_colors;
816
817 rnn_parsefile(db, "adreno.xml");
818 dom[0] = rnn_finddomain(db, variant);
819 dom[1] = rnn_finddomain(db, "AXXX");
820 control_regs = rnn_finddomain(db, control_reg_name);
821
822 buf = (uint32_t *)readfile(file, &sz);
823
824 printf("; Disassembling microcode: %s\n", file);
825 printf("; Version: %08x\n\n", buf[1]);
826 disasm(&buf[1], sz/4 - 1);
827
828 return 0;
829 }