further changes for v1.7: cpu fixes, support for 'hardware' devices, display to vnc
[dcpu16] / vm-dcpu16.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <strings.h>
6 #include <signal.h>
7 #include <errno.h>
8 #include <assert.h>
9 #include <sysexits.h>
10
11 #include <readline/readline.h>
12
13 #include "dcpu16.h"
14 #include "common.h"
15
16 #include "hw_lem1802.h"
17
18 /*
19 * shell-like driver for dcpu16 core
20 * provides a basic interface to control a single emulation instance
21 *
22 * Justin Wind <justin.wind@gmail.com>
23 * 2012 04 10 - implementation started
24 * 2012 04 12 - cleanup, better shell loop
25 * 2012 05 12 - support v1.7 style devices
26 *
27 * TODO
28 * handle quotes in shell command parsing
29 * use readline/history.h, since we're using readline anyhow
30 * ncurses windowing or something, for future display capabilities
31 */
32
33 static const char * const src_id_ = "$Id$";
34
35 /* global invocation options */
36 struct options {
37 unsigned int verbose;
38 } opt_ = {
39 .verbose = 0,
40 };
41
42 /* global run state, first sigint caught will drop out of run loop and back into shell */
43 static volatile unsigned int running_ = 0;
44 static
45 void sigint_handler_(int sig) {
46 (void)sig;
47 running_ = 0;
48 }
49
50 #define VERBOSE_PRINTF(...) do { if (opt_.verbose) printf(__VA_ARGS__); } while (0)
51
52 static
53 void usage_(char *prog, unsigned int full) {
54 FILE *f = full ? stdout : stderr;
55 char *x = strrchr(prog, '/');
56
57 if (x && *(x + 1))
58 prog = x + 1;
59
60 if (full)
61 fprintf(f, "%s -- dcpu16 emulator core shell\n\n",
62 prog);
63
64 fprintf(f, "Usage: %s [-v] [file]\n",
65 prog);
66
67 if (full) {
68 fprintf(f, "\nOptions:\n"
69 "\t [file] -- ram image to load initially\n"
70 "\t -v -- prints slightly more information while operating\n"
71 "\t -h -- this screen\n");
72
73 fprintf(f, "\n%78s\n", src_id_);
74 }
75 }
76
77
78 /* flense a buffer into a newly-allocated argument list */
79 static
80 int buf_tok_vect_(char ***v, int *c, char *buf) {
81 const char *sep = " \t";
82 const char *quot = "\"'`";
83 const size_t v_grow = 32;
84 size_t v_sz = 32;
85 char *st, *qt;
86
87 *c = 0;
88 *v = malloc(v_sz * sizeof **v);
89 if (*v == NULL) {
90 fprintf(stderr, "%s():%s\n", "malloc", strerror(errno));
91 return -1;
92 }
93
94 for ( (*v)[*c] = strqtok_r(buf, sep, '\\', quot, &qt, &st);
95 (*v)[*c];
96 (*v)[*c] = strqtok_r(NULL, sep, '\\', quot, &qt, &st)
97 ) {
98 (*c)++;
99
100 if ((size_t)(*c) == v_sz) {
101 void *tmp_ptr = realloc(*v, (v_sz + v_grow) * sizeof **v);
102 if (tmp_ptr == NULL) {
103 fprintf(stderr, "%s():%s\n", "realloc", strerror(errno));
104 free(*v);
105 *v = NULL;
106 return -1;
107 }
108 v_sz += v_grow;
109 }
110 }
111
112 return 0;
113 }
114
115 /*
116 resets the vm if addr is zero then
117 loads an image from filename into ram starting at addr
118 */
119 static
120 int file_load_(struct dcpu16 *vm, char *filename, DCPU16_WORD addr) {
121 FILE *f;
122 size_t r;
123
124 if (!addr)
125 dcpu16_reset(vm);
126
127 f = fopen(filename, "rb");
128 if (f == NULL) {
129 fprintf(stderr, "%s('%s'):%s\n", "fopen", filename, strerror(errno));
130 return -1;
131 }
132
133 r = fread(vm->ram + addr, sizeof(DCPU16_WORD), DCPU16_RAM - addr, f);
134 VERBOSE_PRINTF("read %zu words", r);
135 if (addr) VERBOSE_PRINTF(" starting at 0x%04x", addr);
136 VERBOSE_PRINTF("\n");
137
138 if (ferror(f))
139 fprintf(stderr, "%s('%s'):%s\n", "fread", filename, strerror(errno));
140
141 fclose(f);
142 return 0;
143 }
144
145
146 /*
147 Here follows the various commands the shell can execute.
148
149 At invocation, a command function will have already had its
150 number of arguments vetted, but will need command-specific
151 argument verifications done.
152
153 The arg_vector contains the command as the first entry, and
154 as such, arg_count will always be at least 1.
155 However, the args_min and args_max entries in struct command_
156 only refer to the counts of arguments, not the entries in the
157 argv.
158 */
159
160 struct command_ {
161 char *name;
162 int args_min;
163 int args_max;
164 int (*func)(struct dcpu16 *, int c, char **v);
165 void (*help)(FILE *f, unsigned int);
166 };
167
168 #define COMMAND_IMPL(x) static int command_##x##_(struct dcpu16 *vm, int arg_count, char **arg_vector)
169 #define COMMAND_HELP(x) static void command_##x##_help_(FILE *f, unsigned int summary)
170 #define COMMAND_ENTRY(x, y, z) { #x, y, z, command_##x##_, command_##x##_help_ }
171
172
173 COMMAND_IMPL(quit) {
174 (void)vm, (void)arg_count, (void)arg_vector;
175
176 return -1;
177 }
178 COMMAND_HELP(quit) {
179 fprintf(f, "\tquit\n");
180 if (summary) return;
181
182 fprintf(f, "Exits the emulator.\n");
183 }
184
185
186 COMMAND_IMPL(reset) {
187 (void)arg_count, (void)arg_vector;
188
189 dcpu16_reset(vm);
190 printf("initialized\n");
191 return 0;
192 }
193 COMMAND_HELP(reset) {
194 fprintf(f, "\treset\n");
195 if (summary) return;
196
197 fprintf(f, "Clears and reinitializes emulator.\n");
198 }
199
200
201 COMMAND_IMPL(load) {
202 int addr = 0;
203
204 if (arg_count > 2) {
205 addr = str_to_word(arg_vector[2]);
206 if (addr < 0) {
207 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[2], strerror(errno));
208 return 0;
209 }
210 }
211
212 if (file_load_(vm, arg_vector[1], addr)) {
213 fprintf(stderr, "failed to load '%s'\n", arg_vector[1]);
214 return 0;
215 }
216 printf("loaded '%s'", arg_vector[1]);
217 if (addr) printf(" starting at 0x%04x", addr);
218 printf("\n");
219
220 return 0;
221 }
222 COMMAND_HELP(load) {
223 fprintf(f, "\tload file [addr]\n");
224 if (summary) return;
225
226 fprintf(f, "Load binary image from 'file' into ram.\n");
227 }
228
229
230 COMMAND_IMPL(dump) {
231 int addr[2];
232 int i;
233
234 for (i = 1; i < arg_count; i++) {
235 addr[i-1] = str_to_word(arg_vector[i]);
236 if (addr[i-1] < 0) {
237 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[i], strerror(errno));
238 return 0;
239 }
240 }
241 if (arg_count < 2) addr[0] = vm->reg[DCPU16_REG_PC];
242 if (arg_count < 3) addr[1] = addr[0];
243
244 if (addr[1] < addr[0]) {
245 fprintf(stderr, "\t'addr_start' must be before addr_end\n");
246 return 0;
247 }
248
249 dcpu16_dump_ram(vm, addr[0], addr[1]);
250
251 return 0;
252 }
253 COMMAND_HELP(dump) {
254 fprintf(f, "\tdump [addr_start [addr_end]]\n");
255 if (summary) return;
256
257 fprintf(f, "Displays contents of ram from addr_start to addr_end.\n");
258 }
259
260
261 COMMAND_IMPL(disassemble) {
262 int addr[2];
263 int i;
264
265 for (i = 1; i < arg_count; i++) {
266 addr[i-1] = str_to_word(arg_vector[i]);
267 if (addr[i-1] < 0) {
268 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[i], strerror(errno));
269 return 0;
270 }
271 }
272 if (arg_count < 2) addr[0] = vm->reg[DCPU16_REG_PC];
273 if (arg_count < 3) addr[1] = addr[0];
274
275 if (addr[1] < addr[0]) {
276 fprintf(stderr, "\t'addr_start' must be before addr_end\n");
277 return 0;
278 }
279
280 for (i = addr[0]; i <= addr[1]; /* */ ) {
281 printf("0x%04x: ", i);
282 i += dcpu16_disassemble_print(vm, i);
283 printf("\n");
284 }
285
286 return 0;
287 }
288 COMMAND_HELP(disassemble) {
289 fprintf(f, "\tdisassemble [addr_start [addr_end]]\n");
290 if (summary) return;
291
292 fprintf(f, "Displays contents of ram parsed into instructions.\n");
293 }
294
295
296 COMMAND_IMPL(step) {
297 unsigned long count = 1;
298 char *ep;
299
300 if (arg_count == 2) {
301 errno = 0;
302 count = strtoul(arg_vector[1], &ep, 0);
303 if (errno
304 || !(*arg_vector[1] && *ep == '\0') ) {
305 fprintf(stderr, "count '%s' is not a valid number: %s\n", arg_vector[1], strerror(errno));
306 return 0;
307 }
308
309 if (count <= 0) {
310 fprintf(stderr, "count must be positive\n");
311 return 0;
312 }
313 }
314
315 while (count--) {
316 dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]);
317 printf("\n");
318 dcpu16_step(vm);
319
320 if (count > 1 && opt_.verbose)
321 dcpu16_state_print(vm);
322 }
323
324 return 0;
325 }
326 COMMAND_HELP(step) {
327 fprintf(f, "\tstep [count]\n");
328 if (summary) return;
329
330 fprintf(f, "Executes the next instruction, or the next count instructions.\n");
331 }
332
333
334 COMMAND_IMPL(set) {
335 int addr, value;
336 DCPU16_WORD *v;
337
338 (void)arg_count;
339
340 /* check if addr is a register */
341 for (addr = 0; dcpu16_reg_names[addr]; addr++) {
342 if (strcasecmp(arg_vector[1], dcpu16_reg_names[addr]) == 0)
343 break;
344 }
345 if (addr < DCPU16_REG__NUM) {
346 v = vm->reg + addr;
347 } else {
348 addr = str_to_word(arg_vector[1]);
349 if (addr < 0) {
350 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[1], strerror(errno));
351 return 0;
352 }
353 v = vm->ram + addr;
354 }
355
356 value = str_to_word(arg_vector[2]);
357 if (value < 0) {
358 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[2], strerror(errno));
359 return 0;
360 }
361
362 *v = value;
363
364 return 0;
365 }
366
367 COMMAND_HELP(set) {
368 fprintf(f, "\tset addr value\n");
369 if (summary) return;
370
371 fprintf(f, "Sets addr to value.");
372 }
373
374 COMMAND_IMPL(run) {
375 struct sigaction act;
376 (void)arg_count, (void)arg_vector;
377
378 running_ = 1;
379
380 memset(&act, 0, sizeof act);
381 act.sa_handler = sigint_handler_;
382 act.sa_flags = SA_RESETHAND;
383
384 if (sigaction(SIGINT, &act, NULL)) {
385 fprintf(stderr, "%s():%s\n", "sigaction", strerror(errno));
386 return -1;
387 }
388
389 while(running_) {
390 dcpu16_step(vm);
391 if (opt_.verbose > 1)
392 dcpu16_state_print(vm);
393 else if (opt_.verbose) {
394 dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]);
395 printf("\n");
396 }
397 }
398
399 printf("interrupted...\n");
400
401 return 0;
402 }
403 COMMAND_HELP(run) {
404 fprintf(f, "\trun\n");
405 if (summary) return;
406
407 fprintf(f, "Begins executing continuously.\n"
408 "May be interrupted with SIGINT.\n");
409 }
410
411 static const char * const display_filename_default_ =
412 #ifdef HAVE_LIBPNG
413 "dcpu16-display.png"
414 #else /* HAVE_LIBPNG */
415 "dcpu16-display.pnm"
416 #endif /* HAVE_LIBPNG */
417 ;
418 COMMAND_IMPL(display) {
419 struct dcpu16_hw *hw = lem1802_new(vm);
420 const char *renderer = arg_vector[1];
421 const char *renderer_arg = NULL;
422 void *renderer_data = NULL;
423
424 if (arg_count == 3)
425 renderer_arg = arg_vector[2];
426
427 if (hw == NULL) {
428 fprintf(stderr, "failed to initialize new display\n");
429 return 0;
430 }
431
432 /* handle per-renderer setup of data.. */
433 /* FIXME: these are awkward */
434 if (strcmp(renderer, "pnm") == 0) {
435 if (renderer_arg == NULL)
436 renderer_arg = display_filename_default_;
437 renderer_data = (void *)renderer_arg;
438 }
439
440 #ifdef HAVE_LIBPNG
441 if (strcmp(renderer, "png") == 0) {
442 if (renderer_arg == NULL)
443 renderer_arg = display_filename_default_;
444 renderer_data = (void *)renderer_arg;
445 }
446 #endif /* HAVE_LIBPNG */
447
448 #ifdef HAVE_LIBVNCSERVER
449 if (strcmp(renderer, "vnc") == 0) {
450 int argc = 1;
451 char *argv[] = { "vm-dcpu16", NULL };
452
453 renderer_data = lem1802_vnc_init_data(argc, argv, hw);
454
455 /* FIXME: keep refs to vnc displays around somewhere, in global list maybe.. */
456 /* keyboards will want to attach to them as well.. */
457
458 if (renderer_data == NULL) {
459 fprintf(stderr, "failed to initialize vnc\n");
460 lem1802_del(&hw);
461 return 0;
462 }
463 }
464 #endif /* HAVE_LIBVNCSERVER */
465
466 if (lem1802_renderer_set(hw, renderer, renderer_data)) {
467 fprintf(stderr, "failed to set back-end renderer for display\n");
468 lem1802_del(&hw);
469 return 0;
470 }
471
472 if (dcpu16_hw_add(vm, hw)) {
473 fprintf(stderr, "failed to attach new display\n");
474 lem1802_del(&hw);
475 return 0;
476 }
477
478 return 0;
479 }
480 COMMAND_HELP(display) {
481 char *name, *args;
482 void *iter;
483
484 fprintf(f, "\tdisplay renderer [renderer data]\n");
485 if (summary) return;
486
487 fprintf(f, "Attaches new display unit, using 'renderer' as back-end output.\n"
488 );
489
490 fprintf(f, "Supported renderers:\n");
491 iter = NULL;
492 while ( (lem1802_renderers_iter(&iter, &name, &args)) ) {
493 fprintf(f, "\t%s %s\n", name, args);
494 }
495 }
496
497 /* gather all these together into a searchable table */
498
499 /* help command gets some assistance in declarations */
500 COMMAND_IMPL(help);
501 COMMAND_HELP(help);
502
503 static struct command_ command_table_[] = {
504 COMMAND_ENTRY(help, 0, -1),
505 COMMAND_ENTRY(quit, 0, -1),
506 COMMAND_ENTRY(load, 1, 2),
507 COMMAND_ENTRY(dump, 0, 2),
508 COMMAND_ENTRY(disassemble, 0, 2),
509 COMMAND_ENTRY(step, 0, 1),
510 COMMAND_ENTRY(run, 0, 0),
511 COMMAND_ENTRY(set, 2, 2),
512 COMMAND_ENTRY(reset, 0, 0),
513 COMMAND_ENTRY(display, 1, 2),
514 { NULL, 0, 0, NULL, NULL }
515 };
516
517 COMMAND_IMPL(help) {
518 struct command_ *c;
519 (void)vm;
520
521 if (arg_count == 2) {
522 for (c = command_table_; c->func; c++) {
523 if (strcasecmp(arg_vector[1], c->name) == 0) {
524 if (c->help)
525 c->help(stdout, 0);
526 break;
527 }
528 }
529 return 0;
530 }
531
532 for (c = command_table_; c->func; c++) {
533 if (c->help)
534 c->help(stdout, 1);
535 }
536 return 0;
537 }
538 COMMAND_HELP(help) {
539 fprintf(f, "\thelp [command]\n");
540 if (summary) return;
541
542 fprintf(f, "Displays a list of available commands, or detailed help on a specific command.\n");
543 }
544
545
546 int main(int argc, char **argv) {
547 const char prompt_fmt[] = "PC:%04x> ";
548 char prompt[32];
549 struct dcpu16 *vm;
550 char *line, *line_prev;
551 char **tok_v, **tok_v_prev;
552 int tok_c, tok_c_prev;
553 int c;
554
555 while ( (c = getopt(argc, argv, "hv")) != EOF) {
556 switch (c) {
557 case 'v':
558 opt_.verbose++;
559 break;
560
561 case 'h':
562 usage_(argv[0], 1);
563 exit(EX_OK);
564
565 default:
566 usage_(argv[0], 0);
567 exit(EX_USAGE);
568 }
569 }
570 if (opt_.verbose < 1) {
571 dcpu16_warn_cb_set(NULL);
572 dcpu16_trace_cb_set(NULL);
573 } else if (opt_.verbose < 2) {
574 dcpu16_trace_cb_set(NULL);
575 }
576 argc -= optind;
577 argv += optind;
578
579 if ((vm = dcpu16_new()) == NULL) {
580 fprintf(stderr, "could not allocate new dcpu16 instance\n");
581 exit(EX_UNAVAILABLE);
582 }
583
584 if (argc) {
585 if (file_load_(vm, *argv, 0)) {
586 fprintf(stderr, "couldn't load '%s'\n", *argv);
587 exit(EX_NOINPUT);
588 }
589 }
590
591 /* show state, read commands */
592 for (line = line_prev = NULL,
593 tok_v = tok_v_prev = NULL,
594 tok_c = tok_c_prev= 0,
595 snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]),
596 dcpu16_state_print(vm);
597
598 (line = readline(prompt));
599
600 printf("\n"),
601 snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]),
602 dcpu16_state_print(vm)) {
603 const char whitespace[] = " \t";
604 char *line_start;
605 struct command_ *c;
606 int r = 0;
607
608 /* skip whitespaces */
609 line_start = line + strspn(line, whitespace);
610
611 if (*line_start) {
612 /* a new command, remember previous for possible repetition */
613
614 /* turn new line into new arg array */
615 if (buf_tok_vect_(&tok_v, &tok_c, line_start)) {
616 fprintf(stderr, "failed to process command\n");
617 continue;
618 }
619
620 /* and keep track if it all for the next time around */
621 if (line_prev) free(line_prev);
622 line_prev = line;
623
624 if (tok_v_prev) free(tok_v_prev);
625 tok_v_prev = tok_v;
626 tok_c_prev = tok_c;
627 } else {
628 /* blank new command, but no prior command to repeat? ask again */
629 if (tok_v_prev == NULL || tok_v_prev[0] == NULL || *(tok_v_prev[0]) == '\0') {
630 free(line);
631 continue;
632 }
633
634 /* otherwise discard new line and promote prior */
635 free(line);
636 tok_v = tok_v_prev;
637 tok_c = tok_c_prev;
638 line = line_prev;
639 }
640
641 /* look up command */
642 for (c = command_table_; c->name; c++) {
643 if (strcasecmp(tok_v[0], c->name) == 0) {
644 if (c->args_min > tok_c - 1) {
645 fprintf(stderr, "%s: not enough arguments\n", c->name);
646 c->help(stderr, 1);
647 break;
648 }
649
650 if (c->args_max > 0
651 && tok_c - 1 > c->args_max) {
652 fprintf(stderr, "%s: too many arguments\n", c->name);
653 c->help(stderr, 1);
654 break;
655 }
656
657 r = c->func(vm, tok_c, tok_v);
658 break;
659 }
660 }
661 if (r)
662 break;
663
664 if (!c->func)
665 fprintf(stderr, "didn't recognize '%s'\n", tok_v[0]);
666 }
667
668 printf("\nfinished\n");
669
670 dcpu16_delete(&vm);
671
672 exit(EX_OK);
673 }