13 #include <readline/readline.h>
18 #include "hw_lem1802.h"
21 * shell-like driver for dcpu16 core
22 * provides a basic interface to control a single emulation instance
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
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
35 static const char * const src_id_
= "$Id$";
37 /* global invocation options */
44 /* global run state, first sigint caught will drop out of run loop and back into shell */
45 static volatile unsigned int running_
= 0;
47 void sigint_handler_(int sig
) {
52 #define VERBOSE_PRINTF(...) do { if (opt_.verbose) printf(__VA_ARGS__); } while (0)
55 void usage_(char *prog
, unsigned int full
) {
56 FILE *f
= full
? stdout
: stderr
;
57 char *x
= strrchr(prog
, '/');
63 fprintf(f
, "%s -- dcpu16 emulator core shell\n\n",
66 fprintf(f
, "Usage: %s [-v] [file]\n",
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");
75 fprintf(f
, "\n%78s\n", src_id_
);
80 /* flense a buffer into a newly-allocated argument list */
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;
90 *v
= malloc(v_sz
* sizeof **v
);
92 fprintf(stderr
, "%s():%s\n", "malloc", strerror(errno
));
96 for ( (*v
)[*c
] = strqtok_r(buf
, sep
, '\\', quot
, &qt
, &st
);
98 (*v
)[*c
] = strqtok_r(NULL
, sep
, '\\', quot
, &qt
, &st
)
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
));
118 resets the vm if addr is zero then
119 loads an image from filename into ram starting at addr
122 int file_load_(struct dcpu16
*vm
, char *filename
, DCPU16_WORD addr
) {
129 f
= fopen(filename
, "rb");
131 fprintf(stderr
, "%s('%s'):%s\n", "fopen", filename
, strerror(errno
));
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");
141 fprintf(stderr
, "%s('%s'):%s\n", "fread", filename
, strerror(errno
));
149 Here follows the various commands the shell can execute.
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.
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
166 int (*func
)(struct dcpu16
*, int c
, char **v
);
167 void (*help
)(FILE *f
, unsigned int);
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_ }
176 (void)vm
, (void)arg_count
, (void)arg_vector
;
181 fprintf(f
, "\tquit\n");
184 fprintf(f
, "Exits the emulator.\n");
188 COMMAND_IMPL(reset
) {
189 (void)arg_count
, (void)arg_vector
;
192 printf("initialized\n");
195 COMMAND_HELP(reset
) {
196 fprintf(f
, "\treset\n");
199 fprintf(f
, "Clears and reinitializes emulator.\n");
207 addr
= str_to_word(arg_vector
[2]);
209 fprintf(stderr
, "address '%s' is not a valid word: %s\n", arg_vector
[2], strerror(errno
));
214 if (file_load_(vm
, arg_vector
[1], addr
)) {
215 fprintf(stderr
, "failed to load '%s'\n", arg_vector
[1]);
218 printf("loaded '%s'", arg_vector
[1]);
219 if (addr
) printf(" starting at 0x%04x", addr
);
225 fprintf(f
, "\tload file [addr]\n");
228 fprintf(f
, "Load binary image from 'file' into ram.\n");
236 for (i
= 1; i
< arg_count
; i
++) {
237 addr
[i
-1] = str_to_word(arg_vector
[i
]);
239 fprintf(stderr
, "address '%s' is not a valid word: %s\n", arg_vector
[i
], strerror(errno
));
243 if (arg_count
< 2) addr
[0] = vm
->reg
[DCPU16_REG_PC
];
244 if (arg_count
< 3) addr
[1] = addr
[0];
246 if (addr
[1] < addr
[0]) {
247 fprintf(stderr
, "\t'addr_start' must be before addr_end\n");
251 dcpu16_dump_ram(vm
, addr
[0], addr
[1]);
256 fprintf(f
, "\tdump [addr_start [addr_end]]\n");
259 fprintf(f
, "Displays contents of ram from addr_start to addr_end.\n");
263 COMMAND_IMPL(disassemble
) {
267 for (i
= 1; i
< arg_count
; i
++) {
268 addr
[i
-1] = str_to_word(arg_vector
[i
]);
270 fprintf(stderr
, "address '%s' is not a valid word: %s\n", arg_vector
[i
], strerror(errno
));
274 if (arg_count
< 2) addr
[0] = vm
->reg
[DCPU16_REG_PC
];
275 if (arg_count
< 3) addr
[1] = addr
[0];
277 if (addr
[1] < addr
[0]) {
278 fprintf(stderr
, "\t'addr_start' must be before addr_end\n");
282 for (i
= addr
[0]; i
<= addr
[1]; /* */ ) {
283 printf("0x%04x: ", i
);
284 i
+= dcpu16_disassemble_print(vm
, i
);
290 COMMAND_HELP(disassemble
) {
291 fprintf(f
, "\tdisassemble [addr_start [addr_end]]\n");
294 fprintf(f
, "Displays contents of ram parsed into instructions.\n");
299 unsigned long count
= 1;
302 if (arg_count
== 2) {
304 count
= strtoul(arg_vector
[1], &ep
, 0);
306 || !(*arg_vector
[1] && *ep
== '\0') ) {
307 fprintf(stderr
, "count '%s' is not a valid number: %s\n", arg_vector
[1], strerror(errno
));
312 fprintf(stderr
, "count must be positive\n");
318 dcpu16_disassemble_print(vm
, vm
->reg
[DCPU16_REG_PC
]);
322 if (count
> 1 && opt_
.verbose
)
323 dcpu16_state_print(vm
);
329 fprintf(f
, "\tstep [count]\n");
332 fprintf(f
, "Executes the next instruction, or the next count instructions.\n");
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)
347 if (addr
< DCPU16_REG__NUM
) {
350 addr
= str_to_word(arg_vector
[1]);
352 fprintf(stderr
, "address '%s' is not a valid word: %s\n", arg_vector
[1], strerror(errno
));
358 value
= str_to_word(arg_vector
[2]);
360 fprintf(stderr
, "address '%s' is not a valid word: %s\n", arg_vector
[2], strerror(errno
));
370 fprintf(f
, "\tset addr value\n");
373 fprintf(f
, "Sets addr to value.");
376 #define MICROSECONDS_PER_CYCLE 10
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
;
385 (void)arg_count
, (void)arg_vector
;
388 gettimeofday(&run_start_tv
, NULL
);
389 run_cycle_start
= vm
->cycle
;
391 memset(&act
, 0, sizeof act
);
392 act
.sa_handler
= sigint_handler_
;
393 act
.sa_flags
= SA_RESETHAND
;
395 if (sigaction(SIGINT
, &act
, NULL
)) {
396 fprintf(stderr
, "%s():%s\n", "sigaction", strerror(errno
));
401 gettimeofday(&start_tv
, NULL
);
402 cycle_start
= vm
->cycle
;
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
]);
412 /* how many cycles did this instr use? */
413 cycles_to_wait
= vm
->cycle
- cycle_start
;
415 if (cycles_to_wait
== 0)
418 /* each cycle wants 10 microseconds */
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;
430 nanosleep(&sleep_time
, NULL
);
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
,
441 printf("interrupted...\n");
446 fprintf(f
, "\trun\n");
449 fprintf(f
, "Begins executing continuously.\n"
450 "May be interrupted with SIGINT.\n");
453 static const char * const display_filename_default_
=
456 #else /* HAVE_LIBPNG */
458 #endif /* HAVE_LIBPNG */
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
;
467 renderer_arg
= arg_vector
[2];
470 fprintf(stderr
, "failed to initialize new display\n");
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
;
483 if (strcmp(renderer
, "png") == 0) {
484 if (renderer_arg
== NULL
)
485 renderer_arg
= display_filename_default_
;
486 renderer_data
= (void *)renderer_arg
;
488 #endif /* HAVE_LIBPNG */
490 #ifdef HAVE_LIBVNCSERVER
491 if (strcmp(renderer
, "vnc") == 0) {
493 char *argv
[] = { "vm-dcpu16", NULL
};
495 renderer_data
= lem1802_vnc_init_data(argc
, argv
, hw
);
497 /* FIXME: keep refs to vnc displays around somewhere, in global list maybe.. */
498 /* keyboards will want to attach to them as well.. */
500 if (renderer_data
== NULL
) {
501 fprintf(stderr
, "failed to initialize vnc\n");
506 #endif /* HAVE_LIBVNCSERVER */
508 if (lem1802_renderer_set(hw
, renderer
, renderer_data
)) {
509 fprintf(stderr
, "failed to set back-end renderer for display\n");
514 if (dcpu16_hw_add(vm
, hw
)) {
515 fprintf(stderr
, "failed to attach new display\n");
522 COMMAND_HELP(display
) {
526 fprintf(f
, "\tdisplay renderer [renderer data]\n");
529 fprintf(f
, "Attaches new display unit, using 'renderer' as back-end output.\n"
532 fprintf(f
, "Supported renderers:\n");
534 while ( (lem1802_renderers_iter(&iter
, &name
, &args
)) ) {
535 fprintf(f
, "\t%s %s\n", name
, args
);
539 /* gather all these together into a searchable table */
541 /* help command gets some assistance in declarations */
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
}
563 if (arg_count
== 2) {
564 for (c
= command_table_
; c
->func
; c
++) {
565 if (strcasecmp(arg_vector
[1], c
->name
) == 0) {
574 for (c
= command_table_
; c
->func
; c
++) {
581 fprintf(f
, "\thelp [command]\n");
584 fprintf(f
, "Displays a list of available commands, or detailed help on a specific command.\n");
588 int main(int argc
, char **argv
) {
589 const char prompt_fmt
[] = "PC:%04x> ";
592 char *line
, *line_prev
;
593 char **tok_v
, **tok_v_prev
;
594 int tok_c
, tok_c_prev
;
597 while ( (c
= getopt(argc
, argv
, "hv")) != EOF
) {
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
);
621 if ((vm
= dcpu16_new()) == NULL
) {
622 fprintf(stderr
, "could not allocate new dcpu16 instance\n");
623 exit(EX_UNAVAILABLE
);
627 if (file_load_(vm
, *argv
, 0)) {
628 fprintf(stderr
, "couldn't load '%s'\n", *argv
);
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
);
640 (line
= readline(prompt
));
643 snprintf(prompt
, sizeof prompt
, prompt_fmt
, vm
->reg
[DCPU16_REG_PC
]),
644 dcpu16_state_print(vm
)) {
645 const char whitespace
[] = " \t";
650 /* skip whitespaces */
651 line_start
= line
+ strspn(line
, whitespace
);
654 /* a new command, remember previous for possible repetition */
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");
662 /* and keep track if it all for the next time around */
663 if (line_prev
) free(line_prev
);
666 if (tok_v_prev
) free(tok_v_prev
);
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') {
676 /* otherwise discard new line and promote prior */
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
);
693 && tok_c
- 1 > c
->args_max
) {
694 fprintf(stderr
, "%s: too many arguments\n", c
->name
);
699 r
= c
->func(vm
, tok_c
, tok_v
);
707 fprintf(stderr
, "didn't recognize '%s'\n", tok_v
[0]);
710 printf("\nfinished\n");