1/*
2 * Copyright © 2021 Google, Inc.
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 <ctype.h>
26#include <inttypes.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <termios.h>
30#include <unistd.h>
31
32#include "freedreno_pm4.h"
33
34#include "emu.h"
35#include "util.h"
36
37/*
38 * Emulator User Interface:
39 *
40 * Handles the user prompts and input parsing.
41 */
42
43static void
44clear_line(void)
45{
46   if (!isatty(STDOUT_FILENO))
47      return;
48   printf("\r                                                           \r");
49}
50
51static int
52readchar(void)
53{
54   static struct termios saved_termios, unbuffered_termios;
55   int c;
56
57   fflush(stdout);
58
59   tcgetattr(STDIN_FILENO, &saved_termios);
60   unbuffered_termios = saved_termios;
61   cfmakeraw(&unbuffered_termios);
62
63   tcsetattr(STDIN_FILENO, TCSANOW, &unbuffered_termios);
64   do {
65      c = getchar();
66   } while (isspace(c));
67   tcsetattr(STDIN_FILENO, TCSANOW, &saved_termios);
68
69   /* TODO, read from script until EOF and then read from stdin: */
70   if (c == -1)
71      exit(0);
72
73   return c;
74}
75
76static const char *
77extract_string(char **buf)
78{
79   char *p = *buf;
80
81   /* eat any leading whitespace: */
82   while (*p && isspace(*p))
83      p++;
84
85   if (!*p)
86      return NULL;
87
88   char *ret = p;
89
90   /* skip to next whitespace: */
91   while (*p && !isspace(*p))
92      p++;
93
94   if (*p)
95      *p = '\0';
96
97   *buf = ++p;
98
99   return ret;
100}
101
102static size_t
103readline(char **p)
104{
105   static char *buf;
106   static size_t n;
107
108   ssize_t ret = getline(&buf, &n, stdin);
109   if (ret < 0)
110      return ret;
111
112   *p = buf;
113   return 0;
114}
115
116static ssize_t
117read_two_values(const char **val1, const char **val2)
118{
119   char *p;
120
121   ssize_t ret = readline(&p);
122   if (ret < 0)
123      return ret;
124
125   *val1 = extract_string(&p);
126   *val2 = extract_string(&p);
127
128   return 0;
129}
130
131static ssize_t
132read_one_value(const char **val)
133{
134   char *p;
135
136   ssize_t ret = readline(&p);
137   if (ret < 0)
138      return ret;
139
140   *val = extract_string(&p);
141
142   return 0;
143}
144
145static void
146dump_gpr_register(struct emu *emu, unsigned n)
147{
148   printf("              GPR:  ");
149   print_dst(n);
150   printf(": ");
151   if (BITSET_TEST(emu->gpr_regs.written, n)) {
152      printdelta("%08x\n", emu->gpr_regs.val[n]);
153   } else {
154      printf("%08x\n", emu->gpr_regs.val[n]);
155   }
156}
157
158static void
159dump_gpr_registers(struct emu *emu)
160{
161   for (unsigned i = 0; i < ARRAY_SIZE(emu->gpr_regs.val); i++) {
162      dump_gpr_register(emu, i);
163   }
164}
165
166static void
167dump_gpu_register(struct emu *emu, unsigned n)
168{
169   printf("              GPU:  ");
170   char *name = afuc_gpu_reg_name(n);
171   if (name) {
172      printf("%s", name);
173      free(name);
174   } else {
175      printf("0x%04x", n);
176   }
177   printf(": ");
178   if (BITSET_TEST(emu->gpu_regs.written, n)) {
179      printdelta("%08x\n", emu->gpu_regs.val[n]);
180   } else {
181      printf("%08x\n", emu->gpu_regs.val[n]);
182   }
183}
184
185static void
186dump_pipe_register(struct emu *emu, unsigned n)
187{
188   printf("              PIPE: ");
189   print_pipe_reg(n);
190   printf(": ");
191   if (BITSET_TEST(emu->pipe_regs.written, n)) {
192      printdelta("%08x\n", emu->pipe_regs.val[n]);
193   } else {
194      printf("%08x\n", emu->pipe_regs.val[n]);
195   }
196}
197
198static void
199dump_control_register(struct emu *emu, unsigned n)
200{
201   printf("              CTRL: ");
202   print_control_reg(n);
203   printf(": ");
204   if (BITSET_TEST(emu->control_regs.written, n)) {
205      printdelta("%08x\n", emu->control_regs.val[n]);
206   } else {
207      printf("%08x\n", emu->control_regs.val[n]);
208   }
209}
210
211static void
212dump_gpumem(struct emu *emu, uintptr_t addr)
213{
214   uint32_t val = emu_mem_read_dword(emu, addr);
215
216   printf("              MEM:  0x%016"PRIxPTR": ", addr);
217   if (addr == emu->gpumem_written) {
218      printdelta("0x%08x\n", val);
219   } else {
220      printf("0x%08x\n", val);
221   }
222}
223
224static void
225emu_write_gpr_prompt(struct emu *emu)
226{
227   clear_line();
228   printf("    GPR register (name or offset) and value: ");
229
230   const char *name;
231   const char *value;
232
233   if (read_two_values(&name, &value))
234      return;
235
236   unsigned offset = afuc_gpr_reg(name);
237   uint32_t val = strtoul(value, NULL, 0);
238
239   emu_set_gpr_reg(emu, offset, val);
240}
241
242static void
243emu_write_control_prompt(struct emu *emu)
244{
245   clear_line();
246   printf("    Control register (name or offset) and value: ");
247
248   const char *name;
249   const char *value;
250
251   if (read_two_values(&name, &value))
252      return;
253
254   unsigned offset = afuc_control_reg(name);
255   uint32_t val = strtoul(value, NULL, 0);
256
257   emu_set_control_reg(emu, offset, val);
258}
259
260static void
261emu_dump_control_prompt(struct emu *emu)
262{
263   clear_line();
264   printf("    Control register (name or offset): ");
265
266   const char *name;
267
268   if (read_one_value(&name))
269      return;
270
271   printf("\n");
272
273   unsigned offset = afuc_control_reg(name);
274   dump_control_register(emu, offset);
275}
276
277static void
278emu_write_gpu_prompt(struct emu *emu)
279{
280   clear_line();
281   printf("    GPU register (name or offset) and value: ");
282
283   const char *name;
284   const char *value;
285
286   if (read_two_values(&name, &value))
287      return;
288
289   unsigned offset = afuc_gpu_reg(name);
290   uint32_t val = strtoul(value, NULL, 0);
291
292   emu_set_gpu_reg(emu, offset, val);
293}
294
295static void
296emu_dump_gpu_prompt(struct emu *emu)
297{
298   clear_line();
299   printf("    GPU register (name or offset): ");
300
301   const char *name;
302
303   if (read_one_value(&name))
304      return;
305
306   printf("\n");
307
308   unsigned offset = afuc_gpu_reg(name);
309   dump_gpu_register(emu, offset);
310}
311
312static void
313emu_write_mem_prompt(struct emu *emu)
314{
315   clear_line();
316   printf("    GPU memory offset and value: ");
317
318   const char *offset;
319   const char *value;
320
321   if (read_two_values(&offset, &value))
322      return;
323
324   uintptr_t addr = strtoull(offset, NULL, 0);
325   uint32_t val = strtoul(value, NULL, 0);
326
327   emu_mem_write_dword(emu, addr, val);
328}
329
330static void
331emu_dump_mem_prompt(struct emu *emu)
332{
333   clear_line();
334   printf("    GPU memory offset: ");
335
336   const char *offset;
337
338   if (read_one_value(&offset))
339      return;
340
341   printf("\n");
342
343   uintptr_t addr = strtoull(offset, NULL, 0);
344   dump_gpumem(emu, addr);
345}
346
347static void
348emu_dump_prompt(struct emu *emu)
349{
350   do {
351      clear_line();
352      printf("  dump: GPR (r)egisters, (c)ontrol register, (g)pu register, (m)emory: ");
353
354      int c = readchar();
355      printf("%c\n", c);
356
357      if (c == 'r') {
358         /* Since there aren't too many GPR registers, just dump
359          * them all:
360          */
361         dump_gpr_registers(emu);
362         break;
363      } else if (c == 'c') {
364         emu_dump_control_prompt(emu);
365         break;
366      } else if (c == 'g') {
367         emu_dump_gpu_prompt(emu);
368         break;
369      } else if (c == 'm') {
370         emu_dump_mem_prompt(emu);
371         break;
372      } else {
373         printf("invalid option: '%c'\n", c);
374         break;
375      }
376   } while (true);
377}
378
379static void
380emu_write_prompt(struct emu *emu)
381{
382   do {
383      clear_line();
384      printf("  write: GPR (r)egister, (c)ontrol register, (g)pu register, (m)emory: ");
385
386      int c = readchar();
387      printf("%c\n", c);
388
389      if (c == 'r') {
390         emu_write_gpr_prompt(emu);
391         break;
392      } else if (c == 'c') {
393         emu_write_control_prompt(emu);
394         break;
395      } else if (c == 'g') {
396         emu_write_gpu_prompt(emu);
397         break;
398      } else if (c == 'm') {
399         emu_write_mem_prompt(emu);
400         break;
401      } else {
402         printf("invalid option: '%c'\n", c);
403         break;
404      }
405   } while (true);
406}
407
408static void
409emu_packet_prompt(struct emu *emu)
410{
411   clear_line();
412   printf("  Enter packet (opc or register name), followed by payload: ");
413   fflush(stdout);
414
415   char *p;
416   if (readline(&p) < 0)
417      return;
418
419   printf("\n");
420
421   const char *name = extract_string(&p);
422
423   /* Read the payload, so we can know the size to generate correct header: */
424   uint32_t payload[0x7f];
425   unsigned cnt = 0;
426
427   do {
428      const char *val = extract_string(&p);
429      if (!val)
430         break;
431
432      assert(cnt < ARRAY_SIZE(payload));
433      payload[cnt++] = strtoul(val, NULL, 0);
434   } while (true);
435
436   uint32_t hdr;
437   if (afuc_pm4_id(name) >= 0) {
438      unsigned opcode = afuc_pm4_id(name);
439      hdr = pm4_pkt7_hdr(opcode, cnt);
440   } else {
441      unsigned regindx = afuc_gpu_reg(name);
442      hdr = pm4_pkt4_hdr(regindx, cnt);
443   }
444
445   ASSERTED bool ret = emu_queue_push(&emu->roq, hdr);
446   assert(ret);
447
448   for (unsigned i = 0; i < cnt; i++) {
449      ASSERTED bool ret = emu_queue_push(&emu->roq, payload[i]);
450      assert(ret);
451   }
452}
453
454void
455emu_main_prompt(struct emu *emu)
456{
457   if (emu->run_mode)
458      return;
459
460   do {
461      clear_line();
462      printf("(s)tep, (r)un, (d)ump, (w)rite, (p)acket, (h)elp, (q)uit: ");
463
464      int c = readchar();
465
466      printf("%c\n", c);
467
468      if (c == 's') {
469         break;
470      } else if (c == 'r') {
471         emu->run_mode = true;
472         break;
473      } else if (c == 'd') {
474         emu_dump_prompt(emu);
475      } else if (c == 'w') {
476         emu_write_prompt(emu);
477      } else if (c == 'p') {
478         emu_packet_prompt(emu);
479      } else if (c == 'h') {
480         printf("  (s)tep   - single step to next instruction\n");
481         printf("  (r)un    - run until next waitin\n");
482         printf("  (d)ump   - dump memory/register menu\n");
483         printf("  (w)rite  - write memory/register menu\n");
484         printf("  (p)acket - inject a pm4 packet\n");
485         printf("  (h)elp   - show this usage message\n");
486         printf("  (q)uit   - exit emulator\n");
487      } else if (c == 'q') {
488         printf("\n");
489         exit(0);
490      } else {
491         printf("invalid option: '%c'\n", c);
492      }
493   } while (true);
494}
495
496void
497emu_clear_state_change(struct emu *emu)
498{
499   memset(emu->control_regs.written, 0, sizeof(emu->control_regs.written));
500   memset(emu->pipe_regs.written,    0, sizeof(emu->pipe_regs.written));
501   memset(emu->gpu_regs.written,     0, sizeof(emu->gpu_regs.written));
502   memset(emu->gpr_regs.written,     0, sizeof(emu->gpr_regs.written));
503   emu->gpumem_written = ~0;
504}
505
506void
507emu_dump_state_change(struct emu *emu)
508{
509   unsigned i;
510
511   if (emu->quiet)
512      return;
513
514   /* Print the GPRs that changed: */
515   BITSET_FOREACH_SET (i, emu->gpr_regs.written, EMU_NUM_GPR_REGS) {
516      dump_gpr_register(emu, i);
517   }
518
519   BITSET_FOREACH_SET (i, emu->gpu_regs.written, EMU_NUM_GPU_REGS) {
520      dump_gpu_register(emu, i);
521   }
522
523   BITSET_FOREACH_SET (i, emu->pipe_regs.written, EMU_NUM_PIPE_REGS) {
524      dump_pipe_register(emu, i);
525   }
526
527   BITSET_FOREACH_SET (i, emu->control_regs.written, EMU_NUM_CONTROL_REGS) {
528      dump_control_register(emu, i);
529   }
530
531   if (emu->gpumem_written != ~0) {
532      dump_gpumem(emu, emu->gpumem_written);
533   }
534}
535