• 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 <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 
print_gpu_reg(uint32_t regbase)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 
printc(const char * c,const char * fmt,...)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 
print_reg(unsigned reg)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 
print_src(unsigned reg)109 static void print_src(unsigned reg)
110 {
111 	print_reg(reg);
112 }
113 
print_dst(unsigned reg)114 static void print_dst(unsigned reg)
115 {
116 	print_reg(reg);
117 }
118 
print_alu_name(afuc_opc opc,uint32_t instr)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 
getpm4(uint32_t id)161 static const char *getpm4(uint32_t id)
162 {
163 	return rnndec_decode_enum(ctx, "adreno_pm4_type3_packets", id);
164 }
165 
166 static inline unsigned
_odd_parity_bit(unsigned val)167 _odd_parity_bit(unsigned val)
168 {
169 	/* See: http://graphics.stanford.edu/~seander/bithacks.html#ParityParallel
170 	 * note that we want odd parity so 0x6996 is inverted.
171 	 */
172 	val ^= val >> 16;
173 	val ^= val >> 8;
174 	val ^= val >> 4;
175 	val &= 0xf;
176 	return (~0x6996 >> val) & 1;
177 }
178 
179 static struct {
180 	uint32_t offset;
181 	uint32_t num_jump_labels;
182 	uint32_t jump_labels[256];
183 } jump_labels[1024];
184 int num_jump_labels;
185 
add_jump_table_entry(uint32_t n,uint32_t offset)186 static void add_jump_table_entry(uint32_t n, uint32_t offset)
187 {
188 	int i;
189 
190 	if (n > 128) /* can't possibly be a PM4 PKT3.. */
191 		return;
192 
193 	for (i = 0; i < num_jump_labels; i++)
194 		if (jump_labels[i].offset == offset)
195 			goto add_label;
196 
197 	num_jump_labels = i + 1;
198 	jump_labels[i].offset = offset;
199 	jump_labels[i].num_jump_labels = 0;
200 
201 add_label:
202 	jump_labels[i].jump_labels[jump_labels[i].num_jump_labels++] = n;
203 	assert(jump_labels[i].num_jump_labels < 256);
204 }
205 
get_jump_table_entry(uint32_t offset)206 static int get_jump_table_entry(uint32_t offset)
207 {
208 	int i;
209 
210 	for (i = 0; i < num_jump_labels; i++)
211 		if (jump_labels[i].offset == offset)
212 			return i;
213 
214 	return -1;
215 }
216 
217 static uint32_t label_offsets[0x512];
218 static int num_label_offsets;
219 
label_idx(uint32_t offset,bool create)220 static int label_idx(uint32_t offset, bool create)
221 {
222 	int i;
223 	for (i = 0; i < num_label_offsets; i++)
224 		if (offset == label_offsets[i])
225 			return i;
226 	if (!create)
227 		return -1;
228 	label_offsets[i] = offset;
229 	num_label_offsets = i+1;
230 	return i;
231 }
232 
233 static const char *
label_name(uint32_t offset,bool allow_jt)234 label_name(uint32_t offset, bool allow_jt)
235 {
236 	static char name[8];
237 	int lidx;
238 
239 	if (allow_jt) {
240 		lidx = get_jump_table_entry(offset);
241 		if (lidx >= 0) {
242 			int j;
243 			for (j = 0; j < jump_labels[lidx].num_jump_labels; j++) {
244 				uint32_t jump_label = jump_labels[lidx].jump_labels[j];
245 				const char *str = getpm4(jump_label);
246 				if (str)
247 					return str;
248 			}
249 			// if we don't find anything w/ known name, maybe we should
250 			// return UNKN%d to at least make it clear that this is some
251 			// sort of jump-table entry?
252 		}
253 	}
254 
255 	lidx = label_idx(offset, false);
256 	if (lidx < 0)
257 		return NULL;
258 	sprintf(name, "l%03d", lidx);
259 	return name;
260 }
261 
262 
263 static uint32_t fxn_offsets[0x512];
264 static int num_fxn_offsets;
265 
fxn_idx(uint32_t offset,bool create)266 static int fxn_idx(uint32_t offset, bool create)
267 {
268 	int i;
269 	for (i = 0; i < num_fxn_offsets; i++)
270 		if (offset == fxn_offsets[i])
271 			return i;
272 	if (!create)
273 		return -1;
274 	fxn_offsets[i] = offset;
275 	num_fxn_offsets = i+1;
276 	return i;
277 }
278 
279 static const char *
fxn_name(uint32_t offset)280 fxn_name(uint32_t offset)
281 {
282 	static char name[8];
283 	int fidx = fxn_idx(offset, false);
284 	if (fidx < 0)
285 		return NULL;
286 	sprintf(name, "fxn%02d", fidx);
287 	return name;
288 }
289 
print_control_reg(uint32_t id)290 static void print_control_reg(uint32_t id)
291 {
292 	if (rnndec_checkaddr(ctx, control_regs, id, 0)) {
293 		struct rnndecaddrinfo *info = rnndec_decodeaddr(ctx, control_regs, id, 0);
294 		printf("@%s", info->name);
295 		free(info->name);
296 		free(info);
297 	} else {
298 		printf("0x%03x", id);
299 	}
300 }
301 
disasm(uint32_t * buf,int sizedwords)302 static void disasm(uint32_t *buf, int sizedwords)
303 {
304 	uint32_t *instrs = buf;
305 	const int jmptbl_start = instrs[1] & 0xffff;
306 	uint32_t *jmptbl = &buf[jmptbl_start];
307 	afuc_opc opc;
308 	bool rep;
309 	int i;
310 
311 
312 	/* parse jumptable: */
313 	for (i = 0; i < 0x80; i++) {
314 		unsigned offset = jmptbl[i];
315 		unsigned n = i;// + CP_NOP;
316 		add_jump_table_entry(n, offset);
317 	}
318 
319 	/* do a pre-pass to find instructions that are potential branch targets,
320 	 * and add labels for them:
321 	 */
322 	for (i = 0; i < jmptbl_start; i++) {
323 		afuc_instr *instr = (void *)&instrs[i];
324 
325 		afuc_get_opc(instr, &opc, &rep);
326 
327 		switch (opc) {
328 		case OPC_BRNEI:
329 		case OPC_BREQI:
330 		case OPC_BRNEB:
331 		case OPC_BREQB:
332 			label_idx(i + instr->br.ioff, true);
333 			break;
334 		case OPC_PREEMPTLEAVE6:
335 			if (gpuver >= 6)
336 				label_idx(instr->call.uoff, true);
337 			break;
338 		case OPC_CALL:
339 			fxn_idx(instr->call.uoff, true);
340 			break;
341 		case OPC_SETSECURE:
342 			/* this implicitly jumps to pc + 3 if successful */
343 			label_idx(i + 3, true);
344 			break;
345 		default:
346 			break;
347 		}
348 	}
349 
350 	/* print instructions: */
351 	for (i = 0; i < jmptbl_start; i++) {
352 		int jump_label_idx;
353 		afuc_instr *instr = (void *)&instrs[i];
354 		const char *fname, *lname;
355 		afuc_opc opc;
356 		bool rep;
357 
358 		afuc_get_opc(instr, &opc, &rep);
359 
360 		lname = label_name(i, false);
361 		fname = fxn_name(i);
362 		jump_label_idx = get_jump_table_entry(i);
363 
364 		if (jump_label_idx >= 0) {
365 			int j;
366 			printf("\n");
367 			for (j = 0; j < jump_labels[jump_label_idx].num_jump_labels; j++) {
368 				uint32_t jump_label = jump_labels[jump_label_idx].jump_labels[j];
369 				const char *name = getpm4(jump_label);
370 				if (name) {
371 					printlbl("%s", name);
372 				} else {
373 					printlbl("UNKN%d", jump_label);
374 				}
375 				printf(":\n");
376 			}
377 		}
378 
379 		if (fname) {
380 			printlbl("%s", fname);
381 			printf(":\n");
382 		}
383 
384 		if (lname) {
385 			printlbl(" %s", lname);
386 			printf(":");
387 		} else {
388 			printf("      ");
389 		}
390 
391 
392 		if (verbose) {
393 			printf("\t%04x: %08x  ", i, instrs[i]);
394 		} else {
395 			printf("  ");
396 		}
397 
398 		switch (opc) {
399 		case OPC_NOP: {
400 			/* a6xx changed the default immediate, and apparently 0
401 			 * is illegal now.
402 			 */
403 			const uint32_t nop = gpuver >= 6 ? 0x1000000 : 0x0;
404 			if (instrs[i] != nop) {
405 				printerr("[%08x]", instrs[i]);
406 				printf("  ; ");
407 			}
408 			if (rep)
409 				printf("(rep)");
410 			printf("nop");
411 			print_gpu_reg(instrs[i]);
412 
413 			break;
414 		}
415 		case OPC_ADD:
416 		case OPC_ADDHI:
417 		case OPC_SUB:
418 		case OPC_SUBHI:
419 		case OPC_AND:
420 		case OPC_OR:
421 		case OPC_XOR:
422 		case OPC_NOT:
423 		case OPC_SHL:
424 		case OPC_USHR:
425 		case OPC_ISHR:
426 		case OPC_ROT:
427 		case OPC_MUL8:
428 		case OPC_MIN:
429 		case OPC_MAX:
430 		case OPC_CMP: {
431 			bool src1 = true;
432 
433 			if (opc == OPC_NOT)
434 				src1 = false;
435 
436 			if (rep)
437 				printf("(rep)");
438 
439 			print_alu_name(opc, instrs[i]);
440 			print_dst(instr->alui.dst);
441 			printf(", ");
442 			if (src1) {
443 				print_src(instr->alui.src);
444 				printf(", ");
445 			}
446 			printf("0x%04x", instr->alui.uimm);
447 			print_gpu_reg(instr->alui.uimm);
448 
449 			/* print out unexpected bits: */
450 			if (verbose) {
451 				if (instr->alui.src && !src1)
452 					printerr("  (src=%02x)", instr->alui.src);
453 			}
454 
455 			break;
456 		}
457 		case OPC_MOVI: {
458 			if (rep)
459 				printf("(rep)");
460 			printf("mov ");
461 			print_dst(instr->movi.dst);
462 			printf(", 0x%04x", instr->movi.uimm);
463 			if (instr->movi.shift)
464 				printf(" << %u", instr->movi.shift);
465 
466 			/* using mov w/ << 16 is popular way to construct a pkt7
467 			 * header to send (for ex, from PFP to ME), so check that
468 			 * case first
469 			 */
470 			if ((instr->movi.shift == 16) &&
471 					((instr->movi.uimm & 0xff00) == 0x7000)) {
472 				unsigned opc, p;
473 
474 				opc = instr->movi.uimm & 0x7f;
475 				p = _odd_parity_bit(opc);
476 
477 				/* So, you'd think that checking the parity bit would be
478 				 * a good way to rule out false positives, but seems like
479 				 * ME doesn't really care.. at least it would filter out
480 				 * things that look like actual legit packets between
481 				 * PFP and ME..
482 				 */
483 				if (1 || p == ((instr->movi.uimm >> 7) & 0x1)) {
484 					const char *name = getpm4(opc);
485 					printf("\t; ");
486 					if (name)
487 						printlbl("%s", name);
488 					else
489 						printlbl("UNKN%u", opc);
490 					break;
491 				}
492 			}
493 
494 			print_gpu_reg(instr->movi.uimm << instr->movi.shift);
495 
496 			break;
497 		}
498 		case OPC_ALU: {
499 			bool src1 = true;
500 
501 			if (instr->alu.alu == OPC_NOT || instr->alu.alu == OPC_MSB)
502 				src1 = false;
503 
504 			if (instr->alu.pad)
505 				printf("[%08x]  ; ", instrs[i]);
506 
507 			if (rep)
508 				printf("(rep)");
509 			if (instr->alu.xmov)
510 				printf("(xmov%d)", instr->alu.xmov);
511 
512 			/* special case mnemonics:
513 			 *   reading $00 seems to always yield zero, and so:
514 			 *      or $dst, $00, $src -> mov $dst, $src
515 			 *   Maybe add one for negate too, ie.
516 			 *      sub $dst, $00, $src ???
517 			 */
518 			if ((instr->alu.alu == OPC_OR) && !instr->alu.src1) {
519 				printf("mov ");
520 				src1 = false;
521 			} else {
522 				print_alu_name(instr->alu.alu, instrs[i]);
523 			}
524 
525 			print_dst(instr->alu.dst);
526 			if (src1) {
527 				printf(", ");
528 				print_src(instr->alu.src1);
529 			}
530 			printf(", ");
531 			print_src(instr->alu.src2);
532 
533 			/* print out unexpected bits: */
534 			if (verbose) {
535 				if (instr->alu.pad)
536 					printerr("  (pad=%01x)", instr->alu.pad);
537 				if (instr->alu.src1 && !src1)
538 					printerr("  (src1=%02x)", instr->alu.src1);
539 			}
540 
541 			/* xmov is a modifier that makes the processor execute up to 3
542 			 * extra mov's after the current instruction. Given an ALU
543 			 * instruction:
544 			 *
545 			 * (xmovN) alu $dst, $src1, $src2
546 			 *
547 			 * In all of the uses in the firmware blob, $dst and $src2 are one
548 			 * of the "special" registers $data, $addr, $addr2. I've observed
549 			 * that if $dst isn't "special" then it's replaced with $00
550 			 * instead of $data, but I haven't checked what happens if $src2
551 			 * isn't "special".  Anyway, in the usual case, the HW produces a
552 			 * count M = min(N, $rem) and then does the following:
553 			 *
554 			 * M = 1:
555 			 * mov $data, $src2
556 			 *
557 			 * M = 2:
558 			 * mov $data, $src2
559 			 * mov $data, $src2
560 			 *
561 			 * M = 3:
562 			 * mov $data, $src2
563 			 * mov $dst, $src2 (special case for CP_CONTEXT_REG_BUNCH)
564 			 * mov $data, $src2
565 			 *
566 			 * It seems to be frequently used in combination with (rep) to
567 			 * provide a kind of hardware-based loop unrolling, and there's
568 			 * even a special case in the ISA to be able to do this with
569 			 * CP_CONTEXT_REG_BUNCH. However (rep) isn't required.
570 			 *
571 			 * This dumps the expected extra instructions, assuming that $rem
572 			 * isn't too small.
573 			 */
574 			if (verbose && instr->alu.xmov) {
575 				for (int i = 0; i < instr->alu.xmov; i++) {
576 					printf("\n        ; mov ");
577 					if (instr->alu.dst < 0x1d)
578 						printf("$00");
579 					else if (instr->alu.xmov == 3 && i == 1)
580 						print_dst(instr->alu.dst);
581 					else
582 						printf("$data");
583 					printf(", ");
584 					print_src(instr->alu.src2);
585 				}
586 			}
587 
588 			break;
589 		}
590 		case OPC_CWRITE6:
591 		case OPC_CREAD6:
592 		case OPC_STORE6:
593 		case OPC_LOAD6: {
594 			if (rep)
595 				printf("(rep)");
596 
597 			bool is_control_reg = true;
598 			if (gpuver >= 6) {
599 				switch (opc) {
600 				case OPC_CWRITE6:
601 					printf("cwrite ");
602 					break;
603 				case OPC_CREAD6:
604 					printf("cread ");
605 					break;
606 				case OPC_STORE6:
607 					is_control_reg = false;
608 					printf("store ");
609 					break;
610 				case OPC_LOAD6:
611 					is_control_reg = false;
612 					printf("load ");
613 					break;
614 				default:
615 					assert(!"unreachable");
616 				}
617 			} else {
618 				switch (opc) {
619 				case OPC_CWRITE5:
620 					printf("cwrite ");
621 					break;
622 				case OPC_CREAD5:
623 					printf("cread ");
624 					break;
625 				default:
626 					fprintf(stderr, "A6xx control opcode on A5xx?\n");
627 					exit(1);
628 				}
629 			}
630 
631 			print_src(instr->control.src1);
632 			printf(", [");
633 			print_src(instr->control.src2);
634 			printf(" + ");
635 			if (is_control_reg && instr->control.flags != 0x4)
636 				print_control_reg(instr->control.uimm);
637 			else
638 				printf("0x%03x", instr->control.uimm);
639 			printf("], 0x%x", instr->control.flags);
640 			break;
641 		}
642 		case OPC_BRNEI:
643 		case OPC_BREQI:
644 		case OPC_BRNEB:
645 		case OPC_BREQB: {
646 			unsigned off = i + instr->br.ioff;
647 
648 			assert(!rep);
649 
650 			/* Since $00 reads back zero, it can be used as src for
651 			 * unconditional branches.  (This only really makes sense
652 			 * for the BREQB.. or possible BRNEI if imm==0.)
653 			 *
654 			 * If bit=0 then branch is taken if *all* bits are zero.
655 			 * Otherwise it is taken if bit (bit-1) is clear.
656 			 *
657 			 * Note the instruction after a jump/branch is executed
658 			 * regardless of whether branch is taken, so use nop or
659 			 * take that into account in code.
660 			 */
661 			if (instr->br.src || (opc != OPC_BRNEB)) {
662 				bool immed = false;
663 
664 				if (opc == OPC_BRNEI) {
665 					printf("brne ");
666 					immed = true;
667 				} else if (opc == OPC_BREQI) {
668 					printf("breq ");
669 					immed = true;
670 				} else if (opc == OPC_BRNEB) {
671 					printf("brne ");
672 				} else if (opc == OPC_BREQB) {
673 					printf("breq ");
674 				}
675 				print_src(instr->br.src);
676 				if (immed) {
677 					printf(", 0x%x,", instr->br.bit_or_imm);
678 				} else {
679 					printf(", b%u,", instr->br.bit_or_imm);
680 				}
681 			} else {
682 				printf("jump");
683 				if (verbose && instr->br.bit_or_imm) {
684 					printerr("  (src=%03x, bit=%03x) ",
685 						instr->br.src, instr->br.bit_or_imm);
686 				}
687 			}
688 
689 			printf(" #");
690 			printlbl("%s", label_name(off, true));
691 			if (verbose)
692 				printf(" (#%d, %04x)", instr->br.ioff, off);
693 			break;
694 		}
695 		case OPC_CALL:
696 			assert(!rep);
697 			printf("call #");
698 			printlbl("%s", fxn_name(instr->call.uoff));
699 			if (verbose) {
700 				printf(" (%04x)", instr->call.uoff);
701 				if (instr->br.bit_or_imm || instr->br.src) {
702 					printerr("  (src=%03x, bit=%03x) ",
703 						instr->br.src, instr->br.bit_or_imm);
704 				}
705 			}
706 			break;
707 		case OPC_RET:
708 			assert(!rep);
709 			if (instr->ret.pad)
710 				printf("[%08x]  ; ", instrs[i]);
711 			if (instr->ret.interrupt)
712 				printf("iret");
713 			else
714 				printf("ret");
715 			break;
716 		case OPC_WIN:
717 			assert(!rep);
718 			if (instr->waitin.pad)
719 				printf("[%08x]  ; ", instrs[i]);
720 			printf("waitin");
721 			if (verbose && instr->waitin.pad)
722 				printerr("  (pad=%x)", instr->waitin.pad);
723 			break;
724 		case OPC_PREEMPTLEAVE6:
725 			if (gpuver < 6) {
726 				printf("[%08x]  ; op38", instrs[i]);
727 			} else {
728 				printf("preemptleave #");
729 				printlbl("%s", label_name(instr->call.uoff, true));
730 			}
731 			break;
732 		case OPC_SETSECURE:
733 			/* Note: This seems to implicitly read the secure/not-secure state
734 			 * to set from the low bit of $02, and implicitly jumps to pc + 3
735 			 * (i.e. skipping the next two instructions) if it succeeds. We
736 			 * print these implicit parameters to make reading the disassembly
737 			 * easier.
738 			 */
739 			if (instr->pad)
740 				printf("[%08x]  ; ", instrs[i]);
741 			printf("setsecure $02, #");
742 			printlbl("%s", label_name(i + 3, true));
743 			break;
744 		default:
745 			printerr("[%08x]", instrs[i]);
746 			printf("  ; op%02x ", opc);
747 			print_dst(instr->alui.dst);
748 			printf(", ");
749 			print_src(instr->alui.src);
750 			print_gpu_reg(instrs[i] & 0xffff);
751 			break;
752 		}
753 		printf("\n");
754 	}
755 
756 	/* print jumptable: */
757 	if (verbose) {
758 		printf(";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n");
759 		printf("; JUMP TABLE\n");
760 		for (i = 0; i < 0x7f; i++) {
761 			int n = i;// + CP_NOP;
762 			uint32_t offset = jmptbl[i];
763 			const char *name = getpm4(n);
764 			printf("%3d %02x: ", n, n);
765 			printf("%04x", offset);
766 			if (name) {
767 				printf("   ; %s", name);
768 			} else {
769 				printf("   ; UNKN%d", n);
770 			}
771 			printf("\n");
772 		}
773 	}
774 }
775 
776 #define CHUNKSIZE 4096
777 
readfile(const char * path,int * sz)778 static char * readfile(const char *path, int *sz)
779 {
780 	char *buf = NULL;
781 	int fd, ret, n = 0;
782 
783 	fd = open(path, O_RDONLY);
784 	if (fd < 0) {
785 		*sz = 0;
786 		return NULL;
787 	}
788 
789 	while (1) {
790 		buf = realloc(buf, n + CHUNKSIZE);
791 		ret = read(fd, buf + n, CHUNKSIZE);
792 		if (ret < 0) {
793 			free(buf);
794 			*sz = 0;
795 			return NULL;
796 		} else if (ret < CHUNKSIZE) {
797 			n += ret;
798 			*sz = n;
799 			return buf;
800 		} else {
801 			n += CHUNKSIZE;
802 		}
803 	}
804 }
805 
usage(void)806 static void usage(void)
807 {
808 	fprintf(stderr, "Usage:\n"
809 			"\tdisasm [-g GPUVER] [-v] [-c] filename.asm\n"
810 			"\t\t-g - specify GPU version (5, etc)\n"
811 			"\t\t-c - use colors\n"
812 			"\t\t-v - verbose output\n"
813 		);
814 	exit(2);
815 }
816 
main(int argc,char ** argv)817 int main(int argc, char **argv)
818 {
819 	uint32_t *buf;
820 	char *file, *control_reg_name;
821 	bool colors = false;
822 	int sz, c;
823 
824 	/* Argument parsing: */
825 	while ((c = getopt (argc, argv, "g:vc")) != -1) {
826 		switch (c) {
827 			case 'g':
828 				gpuver = atoi(optarg);
829 				break;
830 			case 'v':
831 				verbose = true;
832 				break;
833 			case 'c':
834 				colors = true;
835 				break;
836 			default:
837 				usage();
838 		}
839 	}
840 
841 	if (optind >= argc) {
842 		fprintf(stderr, "no file specified!\n");
843 		usage();
844 	}
845 
846 	file = argv[optind];
847 
848 	/* if gpu version not specified, infer from filename: */
849 	if (!gpuver) {
850 		if (strstr(file, "a5")) {
851 			gpuver = 5;
852 		} else if (strstr(file, "a6")) {
853 			gpuver = 6;
854 		}
855 	}
856 
857 	switch (gpuver) {
858 	case 6:
859 		printf("; a6xx microcode\n");
860 		variant = "A6XX";
861 		control_reg_name = "A6XX_CONTROL_REG";
862 		break;
863 	case 5:
864 		printf("; a5xx microcode\n");
865 		variant = "A5XX";
866 		control_reg_name = "A5XX_CONTROL_REG";
867 		break;
868 	default:
869 		fprintf(stderr, "unknown GPU version!\n");
870 		usage();
871 	}
872 
873 	rnn_init();
874 	db = rnn_newdb();
875 
876 	ctx = rnndec_newcontext(db);
877 	ctx->colors = colors ? &envy_def_colors : &envy_null_colors;
878 
879 	rnn_parsefile(db, "adreno.xml");
880 	rnn_prepdb(db);
881 	if (db->estatus)
882 		errx(db->estatus, "failed to parse register database");
883 	dom[0] = rnn_finddomain(db, variant);
884 	dom[1] = rnn_finddomain(db, "AXXX");
885 	control_regs = rnn_finddomain(db, control_reg_name);
886 
887 	rnndec_varadd(ctx, "chip", variant);
888 
889 	buf = (uint32_t *)readfile(file, &sz);
890 
891 	printf("; Disassembling microcode: %s\n", file);
892 	printf("; Version: %08x\n\n", buf[1]);
893 	disasm(&buf[1], sz/4 - 1);
894 
895 	return 0;
896 }
897