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