13 #include <readline/readline.h>
14 #ifdef HAVE_LIBVNCSERVER
16 #endif /* HAVE_LIBVNCSERVER */
21 #include "hw_lem1802.h"
22 #include "hw_keyboard.h"
25 * shell-like driver for dcpu16 core
26 * provides a basic interface to control a single emulation instance
28 * Justin Wind <justin.wind@gmail.com>
29 * 2012 04 10 - implementation started
30 * 2012 04 12 - cleanup, better shell loop
31 * 2012 05 12 - support v1.7 style devices
34 * handle quotes in shell command parsing
35 * use readline/history.h, since we're using readline anyhow
36 * ncurses windowing or something, for future display capabilities
39 static const char * const src_id_
= "$Id$";
41 /* global invocation options */
48 /* global run state, first sigint caught will drop out of run loop and back into shell */
49 static volatile unsigned int running_
= 0;
51 void sigint_handler_(int sig
) {
56 #define VERBOSE_PRINTF(...) do { if (opt_.verbose) printf(__VA_ARGS__); } while (0)
59 void usage_(char *prog
, unsigned int full
) {
60 FILE *f
= full
? stdout
: stderr
;
61 char *x
= strrchr(prog
, '/');
67 fprintf(f
, "%s -- dcpu16 emulator core shell\n\n",
70 fprintf(f
, "Usage: %s [-v] [file]\n",
74 fprintf(f
, "\nOptions:\n"
75 "\t [file] -- ram image to load initially\n"
76 "\t -v -- prints slightly more information while operating\n"
77 "\t -h -- this screen\n");
79 fprintf(f
, "\n%78s\n", src_id_
);
84 /* flense a buffer into a newly-allocated argument list */
86 int buf_tok_vect_(char ***v
, int *c
, char *buf
) {
87 const char *sep
= " \t";
88 const char *quot
= "\"'`";
89 const size_t v_grow
= 32;
94 *v
= malloc(v_sz
* sizeof **v
);
96 fprintf(stderr
, "%s():%s\n", "malloc", strerror(errno
));
100 for ( (*v
)[*c
] = strqtok_r(buf
, sep
, '\\', quot
, &qt
, &st
);
102 (*v
)[*c
] = strqtok_r(NULL
, sep
, '\\', quot
, &qt
, &st
)
106 if ((size_t)(*c
) == v_sz
) {
107 void *tmp_ptr
= realloc(*v
, (v_sz
+ v_grow
) * sizeof **v
);
108 if (tmp_ptr
== NULL
) {
109 fprintf(stderr
, "%s():%s\n", "realloc", strerror(errno
));
122 resets the vm if addr is zero then
123 loads an image from filename into ram starting at addr
126 int file_load_(struct dcpu16
*vm
, char *filename
, DCPU16_WORD addr
) {
133 f
= fopen(filename
, "rb");
135 fprintf(stderr
, "%s('%s'):%s\n", "fopen", filename
, strerror(errno
));
139 r
= fread(vm
->ram
+ addr
, sizeof(DCPU16_WORD
), DCPU16_RAM
- addr
, f
);
140 VERBOSE_PRINTF("read %zu words", r
);
141 if (addr
) VERBOSE_PRINTF(" starting at 0x%04x", addr
);
142 VERBOSE_PRINTF("\n");
145 fprintf(stderr
, "%s('%s'):%s\n", "fread", filename
, strerror(errno
));
152 #ifdef HAVE_LIBVNCSERVER
153 static struct dynamic_array rfbScreens_
;
154 /* wups, kbdAddEvent isn't null by default, so I guess track associations externally */
155 struct rfb_instance_
{
156 rfbScreenInfoPtr screen
;
157 struct dcpu16_hw
*attached_display
;
158 struct dcpu16_hw
*attached_keyboard
;
161 /* locate or allocate the next display with an un-occupied framebuffer */
163 struct rfb_instance_
*rfbScreen_next_available_display_(struct dynamic_array
*rfbScreens
, int argc
, char *argv
[]) {
165 struct rfb_instance_ new_instance
, *s
;
166 struct packed_args_
{
169 } parg
= { argc
, argv
};
171 fprintf(stderr
, "DEBUG: rfbScreens->entries:%zu\n", rfbScreens
->entries
);
173 for (i
= 0; i
< rfbScreens
->entries
; i
++) {
174 s
= (struct rfb_instance_
*)DYNARRAY_ITEM(*rfbScreens
, i
);
175 if (s
->attached_display
== NULL
)
179 if (dcpu16_hw_module_lem1802
.ctl(NULL
, "new_rfbScreen", &parg
, &new_instance
.screen
)) {
180 fprintf(stderr
, "failed to allocate new rfbScreen");
184 new_instance
.attached_display
= NULL
;
185 new_instance
.attached_keyboard
= NULL
;
186 s
= dynarray_add(rfbScreens
, &new_instance
);
190 /* locate or allocate the next display with an un-occupied keyboard */
192 struct rfb_instance_
*rfbScreen_next_available_keyboard_(struct dynamic_array
*rfbScreens
, int argc
, char *argv
[]) {
194 struct rfb_instance_ new_instance
, *s
;
195 struct packed_args_
{
198 } parg
= { argc
, argv
};
200 for (i
= 0; i
< rfbScreens
->entries
; i
++) {
201 s
= (struct rfb_instance_
*)DYNARRAY_ITEM(*rfbScreens
, i
);
202 if (s
->attached_keyboard
== NULL
)
206 if (dcpu16_hw_module_lem1802
.ctl(NULL
, "new_rfbScreen", &parg
, &new_instance
.screen
)) {
207 fprintf(stderr
, "failed to allocate new rfbScreen");
211 new_instance
.attached_display
= NULL
;
212 new_instance
.attached_keyboard
= NULL
;
213 s
= dynarray_add(rfbScreens
, &new_instance
);
217 /* begin serving a screen */
218 void rfbScreen_start(rfbScreenInfoPtr s
) {
220 rfbRunEventLoop(s
, -1, TRUE
);
222 #endif /* HAVE_LIBVNCSERVER */
225 Here follows the various commands the shell can execute.
227 At invocation, a command function will have already had its
228 number of arguments vetted, but will need command-specific
229 argument verifications done.
231 The arg_vector contains the command as the first entry, and
232 as such, arg_count will always be at least 1.
233 However, the args_min and args_max entries in struct command_
234 only refer to the counts of arguments, not the entries in the
242 int (*func
)(struct dcpu16
*, int c
, char **v
);
243 void (*help
)(FILE *f
, unsigned int);
246 #define COMMAND_IMPL(x) static int command_##x##_(struct dcpu16 *vm, int arg_count, char **arg_vector)
247 #define COMMAND_HELP(x) static void command_##x##_help_(FILE *f, unsigned int summary)
248 #define COMMAND_ENTRY(x, y, z) { #x, y, z, command_##x##_, command_##x##_help_ }
252 (void)vm
, (void)arg_count
, (void)arg_vector
;
257 fprintf(f
, "\tquit\n");
260 fprintf(f
, "Exits the emulator.\n");
264 COMMAND_IMPL(reset
) {
265 (void)arg_count
, (void)arg_vector
;
268 printf("initialized\n");
271 COMMAND_HELP(reset
) {
272 fprintf(f
, "\treset\n");
275 fprintf(f
, "Clears and reinitializes emulator.\n");
283 addr
= str_to_word(arg_vector
[2]);
285 fprintf(stderr
, "address '%s' is not a valid word: %s\n", arg_vector
[2], strerror(errno
));
290 if (file_load_(vm
, arg_vector
[1], addr
)) {
291 fprintf(stderr
, "failed to load '%s'\n", arg_vector
[1]);
294 printf("loaded '%s'", arg_vector
[1]);
295 if (addr
) printf(" starting at 0x%04x", addr
);
301 fprintf(f
, "\tload file [addr]\n");
304 fprintf(f
, "Load binary image from 'file' into ram.\n");
312 for (i
= 1; i
< arg_count
; i
++) {
313 addr
[i
-1] = str_to_word(arg_vector
[i
]);
315 fprintf(stderr
, "address '%s' is not a valid word: %s\n", arg_vector
[i
], strerror(errno
));
319 if (arg_count
< 2) addr
[0] = vm
->reg
[DCPU16_REG_PC
];
320 if (arg_count
< 3) addr
[1] = addr
[0];
322 if (addr
[1] < addr
[0]) {
323 fprintf(stderr
, "\t'addr_start' must be before addr_end\n");
327 dcpu16_dump_ram(vm
, addr
[0], addr
[1]);
332 fprintf(f
, "\tdump [addr_start [addr_end]]\n");
335 fprintf(f
, "Displays contents of ram from addr_start to addr_end.\n");
339 COMMAND_IMPL(disassemble
) {
343 for (i
= 1; i
< arg_count
; i
++) {
344 addr
[i
-1] = str_to_word(arg_vector
[i
]);
346 fprintf(stderr
, "address '%s' is not a valid word: %s\n", arg_vector
[i
], strerror(errno
));
350 if (arg_count
< 2) addr
[0] = vm
->reg
[DCPU16_REG_PC
];
351 if (arg_count
< 3) addr
[1] = addr
[0];
353 if (addr
[1] < addr
[0]) {
354 fprintf(stderr
, "\t'addr_start' must be before addr_end\n");
358 for (i
= addr
[0]; i
<= addr
[1]; /* */ ) {
359 printf("0x%04x: ", i
);
360 i
+= dcpu16_disassemble_print(vm
, i
);
366 COMMAND_HELP(disassemble
) {
367 fprintf(f
, "\tdisassemble [addr_start [addr_end]]\n");
370 fprintf(f
, "Displays contents of ram parsed into instructions.\n");
375 unsigned long count
= 1;
378 if (arg_count
== 2) {
380 count
= strtoul(arg_vector
[1], &ep
, 0);
382 || !(*arg_vector
[1] && *ep
== '\0') ) {
383 fprintf(stderr
, "count '%s' is not a valid number: %s\n", arg_vector
[1], strerror(errno
));
388 fprintf(stderr
, "count must be positive\n");
394 dcpu16_disassemble_print(vm
, vm
->reg
[DCPU16_REG_PC
]);
398 if (count
> 1 && opt_
.verbose
)
399 dcpu16_state_print(vm
);
405 fprintf(f
, "\tstep [count]\n");
408 fprintf(f
, "Executes the next instruction, or the next count instructions.\n");
418 /* check if addr is a register */
419 for (addr
= 0; dcpu16_reg_names
[addr
]; addr
++) {
420 if (strcasecmp(arg_vector
[1], dcpu16_reg_names
[addr
]) == 0)
423 if (addr
< DCPU16_REG__NUM
) {
426 addr
= str_to_word(arg_vector
[1]);
428 fprintf(stderr
, "address '%s' is not a valid word: %s\n", arg_vector
[1], strerror(errno
));
434 value
= str_to_word(arg_vector
[2]);
436 fprintf(stderr
, "address '%s' is not a valid word: %s\n", arg_vector
[2], strerror(errno
));
446 fprintf(f
, "\tset addr value\n");
449 fprintf(f
, "Sets addr to value.");
452 #define MICROSECONDS_PER_CYCLE 10
454 struct sigaction act
;
455 struct timeval run_start_tv
, run_stop_tv
;
456 long long run_cycle_start
;
457 struct timeval start_tv
, now_tv
, diff_tv
;
458 long long cycle_start
, cycles_to_wait
;
459 struct timespec sleep_time
, rem_time
;
462 (void)arg_count
, (void)arg_vector
;
465 gettimeofday(&run_start_tv
, NULL
);
466 run_cycle_start
= vm
->cycle_
;
468 memset(&act
, 0, sizeof act
);
469 act
.sa_handler
= sigint_handler_
;
470 act
.sa_flags
= SA_RESETHAND
;
472 if (sigaction(SIGINT
, &act
, NULL
)) {
473 fprintf(stderr
, "%s():%s\n", "sigaction", strerror(errno
));
478 gettimeofday(&start_tv
, NULL
);
479 cycle_start
= vm
->cycle_
;
482 if (opt_
.verbose
> 1)
483 dcpu16_state_print(vm
);
484 else if (opt_
.verbose
) {
485 dcpu16_disassemble_print(vm
, vm
->reg
[DCPU16_REG_PC
]);
489 /* how many cycles did this instr use? */
490 cycles_to_wait
= vm
->cycle_
- cycle_start
;
492 if (cycles_to_wait
== 0)
495 /* each cycle wants 10 microseconds */
497 /* how much of that did we spend already */
498 gettimeofday(&now_tv
, NULL
);
499 timeval_subtract(&diff_tv
, &now_tv
, &start_tv
);
500 /* do we have time to kill? */
501 if (cycles_to_wait
* MICROSECONDS_PER_CYCLE
> diff_tv
.tv_usec
) {
502 sleep_time
.tv_sec
= diff_tv
.tv_sec
;
503 /* this is not accurate.. */
504 sleep_time
.tv_nsec
= 250 * ( (cycles_to_wait
* MICROSECONDS_PER_CYCLE
) - diff_tv
.tv_usec
);
506 /* nanosleep doesn't interfere with libvncserver, unlike usleep */
507 while ( nanosleep(&sleep_time
, &rem_time
) ) {
508 sleep_time
= rem_time
;
509 fprintf(stderr
, "rem:%ld %ld\n", rem_time
.tv_sec
, rem_time
.tv_nsec
);
514 gettimeofday(&run_stop_tv
, NULL
);
515 timeval_subtract(&diff_tv
, &run_stop_tv
, &run_start_tv
);
516 run_usec
= diff_tv
.tv_sec
* 1000000;
517 run_usec
+= diff_tv
.tv_usec
;
518 fprintf(stderr
, "ran %llu cycles in %lds %dus (%lldus)\n",
519 vm
->cycle_
- run_cycle_start
,
524 printf("interrupted...\n");
529 fprintf(f
, "\trun\n");
532 fprintf(f
, "Begins executing continuously.\n"
533 "May be interrupted with SIGINT.\n");
536 static const char * const display_filename_default_
=
539 #else /* HAVE_LIBPNG */
541 #endif /* HAVE_LIBPNG */
543 COMMAND_IMPL(display
) {
544 struct dcpu16_hw
*hw
;
545 const char *renderer
= arg_vector
[1];
546 const char *renderer_arg
= NULL
;
550 renderer_arg
= arg_vector
[2];
552 hw
= dcpu16_hw_new(vm
, &dcpu16_hw_module_lem1802
, NULL
);
554 fprintf(stderr
, "failed to initialize new display\n");
558 /* handle per-renderer setup of data.. */
559 /* FIXME: these are awkward */
560 if (strcmp(renderer
, "pnm") == 0) {
561 renderer_data
= (void *)(renderer_arg
? renderer_arg
: display_filename_default_
);
565 if (strcmp(renderer
, "png") == 0) {
566 renderer_data
= (void *)(renderer_arg
? renderer_arg
: display_filename_default_
);
568 #endif /* HAVE_LIBPNG */
570 #ifdef HAVE_LIBVNCSERVER
571 if (strcmp(renderer
, "vnc") == 0) {
573 char *argv
[] = { "vm-dcpu16", NULL
};
574 struct rfb_instance_
*s
;
576 s
= rfbScreen_next_available_display_(&rfbScreens_
, argc
, argv
);
578 fprintf(stderr
, "failed to initialize vnc\n");
583 if (dcpu16_hw_ctl(hw
, "associate_rfbScreen", s
->screen
, NULL
)) {
584 fprintf(stderr
, "failed to configure display/vnc");
588 s
->attached_display
= hw
;
589 rfbScreen_start(s
->screen
);
590 renderer_data
= s
->screen
;
592 #endif /* HAVE_LIBVNCSERVER */
594 dcpu16_hw_ctl(hw
, "renderer", (char *)renderer
, NULL
);
595 dcpu16_hw_ctl(hw
, "renderer_data", renderer_data
, NULL
);
597 if (dcpu16_hw_attach(vm
, hw
)) {
598 fprintf(stderr
, "failed to attach new display\n");
605 COMMAND_HELP(display
) {
612 fprintf(f
, "\tdisplay renderer [renderer data]\n");
615 fprintf(f
, "Attaches new display unit, using 'renderer' as back-end output.\n"
618 fprintf(f
, "Supported renderers:\n");
622 if (dcpu16_hw_module_lem1802
.ctl(NULL
, "renderers_iter", &iter
, &renderer
)) {
623 fprintf(stderr
, "error fetching next renderer\n");
626 if (iter
== NULL
|| renderer
.name
== NULL
)
629 fprintf(f
, "\t%s %s\n", renderer
.name
, renderer
.args
);
633 COMMAND_IMPL(keyboard
) {
634 struct dcpu16_hw
*hw
;
636 (void)arg_count
, (void)arg_vector
;
638 hw
= dcpu16_hw_new(vm
, &dcpu16_hw_module_keyboard
, NULL
);
640 fprintf(stderr
, "failed to initialize new keyboard\n");
644 #ifdef HAVE_LIBVNCSERVER
645 struct rfb_instance_
*s
;
647 char *argv
[] = { "vm-dcpu16", NULL
};
649 s
= rfbScreen_next_available_keyboard_(&rfbScreens_
, argc
, argv
);
651 fprintf(stderr
, "failed to initialize vnc\n");
655 if (dcpu16_hw_ctl(hw
, "associate_rfbScreen", s
->screen
, NULL
)) {
656 fprintf(stderr
, "failed to configure keyboard/vnc\n");
660 s
->attached_keyboard
= hw
;
662 if (dcpu16_hw_attach(vm
, hw
)) {
663 fprintf(stderr
, "failed to attach new keyboard\n");
667 #endif /* HAVE_LIBVNCSERVER */
671 COMMAND_HELP(keyboard
) {
672 fprintf(f
, "\tkeyboard\n");
675 fprintf(f
, "Attaches new keyboard unit.\n");
678 /* gather all these together into a searchable table */
680 /* help command gets some assistance in declarations */
684 static struct command_ command_table_
[] = {
685 COMMAND_ENTRY(help
, 0, -1),
686 COMMAND_ENTRY(quit
, 0, -1),
687 COMMAND_ENTRY(load
, 1, 2),
688 COMMAND_ENTRY(dump
, 0, 2),
689 COMMAND_ENTRY(disassemble
, 0, 2),
690 COMMAND_ENTRY(step
, 0, 1),
691 COMMAND_ENTRY(run
, 0, 0),
692 COMMAND_ENTRY(set
, 2, 2),
693 COMMAND_ENTRY(reset
, 0, 0),
694 COMMAND_ENTRY(display
, 1, 2),
695 COMMAND_ENTRY(keyboard
, 0, 0),
696 { NULL
, 0, 0, NULL
, NULL
}
703 if (arg_count
== 2) {
704 for (c
= command_table_
; c
->func
; c
++) {
705 if (strcasecmp(arg_vector
[1], c
->name
) == 0) {
714 for (c
= command_table_
; c
->func
; c
++) {
721 fprintf(f
, "\thelp [command]\n");
724 fprintf(f
, "Displays a list of available commands, or detailed help on a specific command.\n");
728 int main(int argc
, char **argv
) {
729 const char prompt_fmt
[] = "PC:%04x> ";
732 char *line
, *line_prev
;
733 char **tok_v
, **tok_v_prev
;
734 int tok_c
, tok_c_prev
;
737 while ( (c
= getopt(argc
, argv
, "hv")) != EOF
) {
752 if (opt_
.verbose
< 1) {
753 dcpu16_warn_cb_set(NULL
);
754 dcpu16_trace_cb_set(NULL
);
755 } else if (opt_
.verbose
< 2) {
756 dcpu16_trace_cb_set(NULL
);
761 if ((vm
= dcpu16_new()) == NULL
) {
762 fprintf(stderr
, "could not allocate new dcpu16 instance\n");
763 exit(EX_UNAVAILABLE
);
766 #ifdef HAVE_LIBVNCSERVER
767 if (dynarray_init(&rfbScreens_
, sizeof(struct rfb_instance_
), 4)) {
768 fprintf(stderr
, "could not allocate rfb container\n");
769 exit(EX_UNAVAILABLE
);
771 #endif /* HAVE_LIBVNCSERVER */
774 if (file_load_(vm
, *argv
, 0)) {
775 fprintf(stderr
, "couldn't load '%s'\n", *argv
);
780 /* show state, read commands */
781 for (line
= line_prev
= NULL
,
782 tok_v
= tok_v_prev
= NULL
,
783 tok_c
= tok_c_prev
= 0,
784 snprintf(prompt
, sizeof prompt
, prompt_fmt
, vm
->reg
[DCPU16_REG_PC
]),
785 dcpu16_state_print(vm
);
787 (line
= readline(prompt
));
790 snprintf(prompt
, sizeof prompt
, prompt_fmt
, vm
->reg
[DCPU16_REG_PC
]),
791 dcpu16_state_print(vm
)) {
792 const char whitespace
[] = " \t";
797 /* skip whitespaces */
798 line_start
= line
+ strspn(line
, whitespace
);
801 /* a new command, remember previous for possible repetition */
803 /* turn new line into new arg array */
804 if (buf_tok_vect_(&tok_v
, &tok_c
, line_start
)) {
805 fprintf(stderr
, "failed to process command\n");
809 /* and keep track if it all for the next time around */
810 if (line_prev
) free(line_prev
);
813 if (tok_v_prev
) free(tok_v_prev
);
817 /* blank new command, but no prior command to repeat? ask again */
818 if (tok_v_prev
== NULL
|| tok_v_prev
[0] == NULL
|| *(tok_v_prev
[0]) == '\0') {
823 /* otherwise discard new line and promote prior */
830 /* look up command */
831 for (c
= command_table_
; c
->name
; c
++) {
832 if (strcasecmp(tok_v
[0], c
->name
) == 0) {
833 if (c
->args_min
> tok_c
- 1) {
834 fprintf(stderr
, "%s: not enough arguments\n", c
->name
);
840 && tok_c
- 1 > c
->args_max
) {
841 fprintf(stderr
, "%s: too many arguments\n", c
->name
);
846 r
= c
->func(vm
, tok_c
, tok_v
);
854 fprintf(stderr
, "didn't recognize '%s'\n", tok_v
[0]);
857 printf("\nfinished\n");