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