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 #include "parser.h"
40 #include "asm.h"
41 
42 int gpuver;
43 
44 
45 static struct rnndeccontext *ctx;
46 static struct rnndb *db;
47 static struct rnndomain *control_regs;
48 struct rnndomain *dom[2];
49 
50 
51 /* bit lame to hard-code max but fw sizes are small */
52 static struct asm_instruction instructions[0x2000];
53 static unsigned num_instructions;
54 
55 static struct asm_label labels[0x512];
56 static unsigned num_labels;
57 
next_instr(int tok)58 struct asm_instruction *next_instr(int tok)
59 {
60 	struct asm_instruction *ai = &instructions[num_instructions++];
61 	assert(num_instructions < ARRAY_SIZE(instructions));
62 	ai->tok = tok;
63 	return ai;
64 }
65 
decl_label(const char * str)66 void decl_label(const char *str)
67 {
68 	struct asm_label *label = &labels[num_labels++];
69 
70 	assert(num_labels < ARRAY_SIZE(labels));
71 
72 	label->offset = num_instructions;
73 	label->label = str;
74 }
75 
resolve_label(const char * str)76 static int resolve_label(const char *str)
77 {
78 	int i;
79 
80 	for (i = 0; i < num_labels; i++) {
81 		struct asm_label *label = &labels[i];
82 
83 		if (!strcmp(str, label->label)) {
84 			return label->offset;
85 		}
86 	}
87 
88 	fprintf(stderr, "Undeclared label: %s\n", str);
89 	exit(2);
90 }
91 
tok2alu(int tok)92 static afuc_opc tok2alu(int tok)
93 {
94 	switch (tok) {
95 	case T_OP_ADD:   return OPC_ADD;
96 	case T_OP_ADDHI: return OPC_ADDHI;
97 	case T_OP_SUB:   return OPC_SUB;
98 	case T_OP_SUBHI: return OPC_SUBHI;
99 	case T_OP_AND:   return OPC_AND;
100 	case T_OP_OR:    return OPC_OR;
101 	case T_OP_XOR:   return OPC_XOR;
102 	case T_OP_NOT:   return OPC_NOT;
103 	case T_OP_SHL:   return OPC_SHL;
104 	case T_OP_USHR:  return OPC_USHR;
105 	case T_OP_ISHR:  return OPC_ISHR;
106 	case T_OP_ROT:   return OPC_ROT;
107 	case T_OP_MUL8:  return OPC_MUL8;
108 	case T_OP_MIN:   return OPC_MIN;
109 	case T_OP_MAX:   return OPC_MAX;
110 	case T_OP_CMP:   return OPC_CMP;
111 	case T_OP_MSB:   return OPC_MSB;
112 	default:
113 		assert(0);
114 		return -1;
115 	}
116 }
117 
emit_instructions(int outfd)118 static void emit_instructions(int outfd)
119 {
120 	int i;
121 
122 	/* there is an extra 0x00000000 which kernel strips off.. we could
123 	 * perhaps use it for versioning.
124 	 */
125 	i = 0;
126 	write(outfd, &i, 4);
127 
128 	for (i = 0; i < num_instructions; i++) {
129 		struct asm_instruction *ai = &instructions[i];
130 		afuc_instr instr = {0};
131 		afuc_opc opc;
132 
133 		/* special case, 2nd dword is patched up w/ # of instructions
134 		 * (ie. offset of jmptbl)
135 		 */
136 		if (i == 1) {
137 			assert(ai->is_literal);
138 			ai->literal &= ~0xffff;
139 			ai->literal |= num_instructions;
140 		}
141 
142 		if (ai->is_literal) {
143 			write(outfd, &ai->literal, 4);
144 			continue;
145 		}
146 
147 		switch (ai->tok) {
148 		case T_OP_NOP:
149 			opc = OPC_NOP;
150 			if (gpuver >= 6)
151 				instr.pad = 0x1000000;
152 			break;
153 		case T_OP_ADD:
154 		case T_OP_ADDHI:
155 		case T_OP_SUB:
156 		case T_OP_SUBHI:
157 		case T_OP_AND:
158 		case T_OP_OR:
159 		case T_OP_XOR:
160 		case T_OP_NOT:
161 		case T_OP_SHL:
162 		case T_OP_USHR:
163 		case T_OP_ISHR:
164 		case T_OP_ROT:
165 		case T_OP_MUL8:
166 		case T_OP_MIN:
167 		case T_OP_MAX:
168 		case T_OP_CMP:
169 		case T_OP_MSB:
170 			if (ai->has_immed) {
171 				/* MSB overlaps with STORE */
172 				assert(ai->tok != T_OP_MSB);
173 				opc = tok2alu(ai->tok);
174 				instr.alui.dst = ai->dst;
175 				instr.alui.src = ai->src1;
176 				instr.alui.uimm = ai->immed;
177 			} else {
178 				opc = OPC_ALU;
179 				instr.alu.dst  = ai->dst;
180 				instr.alu.src1 = ai->src1;
181 				instr.alu.src2 = ai->src2;
182 				instr.alu.alu = tok2alu(ai->tok);
183 			}
184 			break;
185 		case T_OP_MOV:
186 			/* move can either be encoded as movi (ie. move w/ immed) or
187 			 * an alu instruction
188 			 */
189 			if (ai->has_immed) {
190 				opc = OPC_MOVI;
191 				instr.movi.dst = ai->dst;
192 				instr.movi.uimm = ai->immed;
193 				instr.movi.shift = ai->shift;
194 			} else if (ai->label) {
195 				/* mov w/ a label is just an alias for an immediate, this
196 				 * is useful to load the address of a constant table into
197 				 * a register:
198 				 */
199 				opc = OPC_MOVI;
200 				instr.movi.dst = ai->dst;
201 				instr.movi.uimm = resolve_label(ai->label);
202 				instr.movi.shift = ai->shift;
203 			} else {
204 				/* encode as: or $dst, $00, $src */
205 				opc = OPC_ALU;
206 				instr.alu.dst  = ai->dst;
207 				instr.alu.src1 = 0x00;      /* $00 reads-back 0 */
208 				instr.alu.src2 = ai->src1;
209 				instr.alu.alu = OPC_OR;
210 			}
211 			break;
212 		case T_OP_CWRITE:
213 		case T_OP_CREAD:
214 		case T_OP_STORE:
215 		case T_OP_LOAD:
216 			if (gpuver >= 6) {
217 				if (ai->tok == T_OP_CWRITE) {
218 					opc = OPC_CWRITE6;
219 				} else if (ai->tok == T_OP_CREAD) {
220 					opc = OPC_CREAD6;
221 				} else if (ai->tok == T_OP_STORE) {
222 					opc = OPC_STORE6;
223 				} else if (ai->tok == T_OP_LOAD) {
224 					opc = OPC_LOAD6;
225 				}
226 			} else {
227 				if (ai->tok == T_OP_CWRITE) {
228 					opc = OPC_CWRITE5;
229 				} else if (ai->tok == T_OP_CREAD) {
230 					opc = OPC_CREAD5;
231 				} else if (ai->tok == T_OP_STORE ||
232 					   ai->tok == T_OP_LOAD) {
233 					fprintf(stderr, "load and store do not exist on a5xx\n");
234 					exit(1);
235 				}
236 			}
237 			instr.control.src1 = ai->src1;
238 			instr.control.src2 = ai->src2;
239 			instr.control.flags = ai->bit;
240 			instr.control.uimm = ai->immed;
241 			break;
242 		case T_OP_BRNE:
243 		case T_OP_BREQ:
244 			if (ai->has_immed) {
245 				opc = (ai->tok == T_OP_BRNE) ? OPC_BRNEI : OPC_BREQI;
246 				instr.br.bit_or_imm = ai->immed;
247 			} else {
248 				opc = (ai->tok == T_OP_BRNE) ? OPC_BRNEB : OPC_BREQB;
249 				instr.br.bit_or_imm = ai->bit;
250 			}
251 			instr.br.src = ai->src1;
252 			instr.br.ioff = resolve_label(ai->label) - i;
253 			break;
254 		case T_OP_RET:
255 			opc = OPC_RET;
256 			break;
257 		case T_OP_CALL:
258 			opc = OPC_CALL;
259 			instr.call.uoff = resolve_label(ai->label);
260 			break;
261 		case T_OP_PREEMPTLEAVE:
262 			opc = OPC_PREEMPTLEAVE6;
263 			instr.call.uoff = resolve_label(ai->label);
264 			break;
265 		case T_OP_JUMP:
266 			/* encode jump as: brne $00, b0, #label */
267 			opc = OPC_BRNEB;
268 			instr.br.bit_or_imm = 0;
269 			instr.br.src = 0x00;       /* $00 reads-back 0.. compare to 0 */
270 			instr.br.ioff = resolve_label(ai->label) - i;
271 			break;
272 		case T_OP_WAITIN:
273 			opc = OPC_WIN;
274 			break;
275 		default:
276 			assert(0);
277 		}
278 
279 		afuc_set_opc(&instr, opc, ai->rep);
280 
281 		write(outfd, &instr, 4);
282 	}
283 
284 }
285 
find_enum_val(struct rnnenum * en,const char * name)286 static int find_enum_val(struct rnnenum *en, const char *name)
287 {
288 	int i;
289 
290 	for (i = 0; i < en->valsnum; i++)
291 		if (en->vals[i]->valvalid && !strcmp(name, en->vals[i]->name))
292 			return en->vals[i]->value;
293 
294 	return -1;
295 }
296 
find_reg(struct rnndomain * dom,const char * name)297 static int find_reg(struct rnndomain *dom, const char *name)
298 {
299 	int i;
300 
301 	for (i = 0; i < dom->subelemsnum; i++)
302 		if (!strcmp(name, dom->subelems[i]->name))
303 			return dom->subelems[i]->offset;
304 
305 	return -1;
306 }
307 
parse_control_reg(const char * name)308 unsigned parse_control_reg(const char *name)
309 {
310 	/* skip leading "@" */
311 	int val = find_reg(control_regs, name + 1);
312 	if (val < 0) {
313 		printf("invalid control reg: %s\n", name);
314 		exit(2);
315 	}
316 	return (unsigned)val;
317 }
318 
emit_jumptable(int outfd)319 static void emit_jumptable(int outfd)
320 {
321 	struct rnnenum *en = rnn_findenum(ctx->db, "adreno_pm4_type3_packets");
322 	uint32_t jmptable[0x80] = {0};
323 	int i;
324 
325 	for (i = 0; i < num_labels; i++) {
326 		struct asm_label *label = &labels[i];
327 		int id = find_enum_val(en, label->label);
328 
329 		/* if it doesn't match a known PM4 packet-id, try to match UNKN%d: */
330 		if (id < 0) {
331 			if (sscanf(label->label, "UNKN%d", &id) != 1) {
332 				/* if still not found, must not belong in jump-table: */
333 				continue;
334 			}
335 		}
336 
337 		jmptable[id] = label->offset;
338 	}
339 
340 	write(outfd, jmptable, sizeof(jmptable));
341 }
342 
usage(void)343 static void usage(void)
344 {
345 	fprintf(stderr, "Usage:\n"
346 			"\tasm [-g GPUVER] filename.asm filename.fw\n"
347 			"\t\t-g - specify GPU version (5, etc)\n"
348 		);
349 	exit(2);
350 }
351 
main(int argc,char ** argv)352 int main(int argc, char **argv)
353 {
354 	FILE *in;
355 	char *file, *outfile, *name, *control_reg_name;
356 	int c, ret, outfd;
357 
358 	/* Argument parsing: */
359 	while ((c = getopt (argc, argv, "g:")) != -1) {
360 		switch (c) {
361 			case 'g':
362 				gpuver = atoi(optarg);
363 				break;
364 			default:
365 				usage();
366 		}
367 	}
368 
369 	if (optind >= (argc + 1)) {
370 		fprintf(stderr, "no file specified!\n");
371 		usage();
372 	}
373 
374 	file = argv[optind];
375 	outfile = argv[optind + 1];
376 
377 	outfd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
378 	if (outfd < 0) {
379 		fprintf(stderr, "could not open \"%s\"\n", outfile);
380 		usage();
381 	}
382 
383 	in = fopen(file, "r");
384 	if (!in) {
385 		fprintf(stderr, "could not open \"%s\"\n", file);
386 		usage();
387 	}
388 
389 	yyset_in(in);
390 
391 	/* if gpu version not specified, infer from filename: */
392 	if (!gpuver) {
393 		if (strstr(file, "a5")) {
394 			gpuver = 5;
395 		} else if (strstr(file, "a6")) {
396 			gpuver = 6;
397 		}
398 	}
399 
400 	switch (gpuver) {
401 	case 6:
402 		name = "A6XX";
403 		control_reg_name = "A6XX_CONTROL_REG";
404 		break;
405 	case 5:
406 		name = "A5XX";
407 		control_reg_name = "A5XX_CONTROL_REG";
408 		break;
409 	default:
410 		fprintf(stderr, "unknown GPU version!\n");
411 		usage();
412 	}
413 
414 	rnn_init();
415 	db = rnn_newdb();
416 
417 	ctx = rnndec_newcontext(db);
418 
419 	rnn_parsefile(db, "adreno.xml");
420 	rnn_prepdb(db);
421 	if (db->estatus)
422 		errx(db->estatus, "failed to parse register database");
423 	dom[0] = rnn_finddomain(db, name);
424 	dom[1] = rnn_finddomain(db, "AXXX");
425 	control_regs = rnn_finddomain(db, control_reg_name);
426 
427 	ret = yyparse();
428 	if (ret) {
429 		fprintf(stderr, "parse failed: %d\n", ret);
430 		return ret;
431 	}
432 
433 	emit_instructions(outfd);
434 	emit_jumptable(outfd);
435 
436 	close(outfd);
437 
438 	return 0;
439 }
440