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