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