From f727d413bf03e1277a96a87c3fe9a66e38a726d1 Mon Sep 17 00:00:00 2001 From: d_m Date: Wed, 21 Feb 2024 12:29:35 -0500 Subject: [PATCH] Add better multiprocess capabilities to uxn11. The changes are summarized in doc/console.txt. --- doc/console.txt | 150 ++++++++++++++ src/devices/console.c | 449 +++++++++++++++++++++++++++--------------- src/devices/console.h | 43 +++- src/devices/system.c | 51 ++++- src/uxn11.c | 47 ++++- src/uxncli.c | 57 +++++- 6 files changed, 614 insertions(+), 183 deletions(-) create mode 100644 doc/console.txt diff --git a/doc/console.txt b/doc/console.txt new file mode 100644 index 0000000..6ef7f62 --- /dev/null +++ b/doc/console.txt @@ -0,0 +1,150 @@ +CONSOLE DEVICE + + 0x10 vector* 0x18 @stdout + 0x11 (vector) 0x19 @stderr + 0x12 stdin 0x1a @proc-put + 0x13 0x1b + 0x14 0x1c param* + 0x15 0x1d (param) + 0x16 0x1e opts + 0x17 type 0x1f @host-put + +(* denotes a short register, i.e. two bytes wide) +(@ denotes a register that causes an immediate effect) + +vector ports (0x10-0x11) contain an address to jump to when input is +available. when the vector fires one byte can be read from the read +port (0x12), and its meaning is defined by the type (0x17). + +the stdin port (0x12) contains one byte of data to be read. the byte +represents input read from one of the following: arguments, stdin, +child processes, or host messages. the type port (0x17) says which. + +the type (0x17) field explains how to interpret calls to the console +vector (0x10) and where data can be read from: + + - 0x00 - 00000000 - no input (-) + - 0x01 - 00000001 - stdin (stdin) + - 0x02 - 00000010 - argument (stdin) + - 0x03 - 00000011 - argument spacer (-) + - 0x04 - 00000100 - argument end (-) + - 0x05 - 00000101 - host response + - 0x06 - 00000110 - host response end (-) + - 0x07 - 00000111 - stdin end (-) + - 0x20 - 00100000 - child 0 sent data + - 0x21 - 00100001 - child 1 sent data + - 0x22 - 00100010 - child 2 sent data + - 0x23 - 00100011 - child 3 sent data + - 0x40 - 01000000 - child 0 data end + - 0x41 - 01000001 - child 1 data end + - 0x42 - 01000010 - child 2 data end + - 0x43 - 01000011 - child 3 data end + - 0x80 - 10000000 - child 0 exited (stdin contains exit code) + - 0x81 - 10000001 - child 1 exited (stdin contains exit code) + - 0x82 - 10000010 - child 2 exited (stdin contains exit code) + - 0x83 - 10000011 - child 3 exited (stdin contains exit code) + +writing a byte to the stdout port (0x18) will send one byte of data to +the emulator's stdout. + +writing a byte to the stderr port (0x19) will send one byte of data to +the emulator's stderr. + +writing a byte to the proc-put port (0x1a) will send one byte of data +to one of the emulator's child processes. the lower 2 bits of the opts +port (0x1e) determine which one: + + - 0x00: child 0 + - 0x01: child 1 + - 0x02: child 2 + - 0x03: child 3 + +the param ports (0x1c-0x1d) specify the a value to use as a parameter +for a host-put (0x1f). the meaning values by host-put value: + + - 0x00 - (nop) unused + - 0x01 - (execute) command string (e.g. 'gcc -o "demo" demo.c') + - 0x02 - (getpid) unused + - 0x03 - (kill) unused + - 0x04 - (raw-tty) unused + - 0x04 - (restore-tty) unused + - 0x10 - (getenv) name string (e.g. "TERM") + - 0x11 - (setenv) assignment string (e.g. "TERM=vt100") + +(strings must be null-terminated. commands are parsed by /bin/sh -c.) + +the opts port (0x1e) specifies options that affect host actions run +using the host-put port (0x1f): + + - lower 2 bits control which child process to use (when applicable) + + 0x00 - child 0 + + 0x01 - child 1 + + 0x02 - child 2 + + 0x03 - child 3 + - next 2 bits (0x0c) are unused + - upper 4 bits control which pipes to use with execute: + + 0x80 - use child's pty (implies 0x70) + + 0x40 - read from child's stderr + + 0x20 - read from child's stdout + + 0x10 - write to child's stdin + +the host-put port (0x1f) specifies which host action to take: + + - 0x00 - nop: does nothing + - 0x01 - execute: reads command string, starts a subprocess + - 0x02 - getpid: responds with child process pid (if any) + - 0x03 - kill: shuts down child process + - 0x04 - put stdin tty into raw mode + - 0x05 - restore stdin tty to canonical mode + - 0x10 - getenv: looks up a name (e.g. TERM) in env, responds with value + - 0x11 - setenv: reads an assignment (e.g. TERM=vt100), updates env + +SYSTEM EXPANSION SUPPORT + +the system expansion port can be used to determine whether or not the +console supports these additional capabilities. the convention is to +use UUID-based extensions as defined by uxn38. the unix console device +uses `01231250-d878-4462-bc41-d0927645a2fa` as its UUID, and uses the +following expansion layout: + + - operation (1 byte): 03 (uuid extension) + - device (1 byte): 10 (console) + - uuid (16 bytes): 0123 1250 d878 4462 bc41 d092 7645 a2fa + - version (1 byte): 00 (set to non-zero by supporting emulators) + - flags (2 byte): 0000 (updated by supporting emulators) + +after being loaded, version will be non-zero if console expansion is +supported, and zero otherwise. flags will contain bits describing the +available capabilities (0x0000 if console expansion is unsupported). +the flags are as follows: + + - 0x0001: supports `execute` + - 0x0002: supports `kill` + - 0x0004: supports `raw-tty`, `restore-tty` + - 0x0008: supports `getenv` and `setenv` + - 0x0010: supports writing to child stdin + - 0x0020: supports reading from child stdout + - 0x0040: supports reading from child stderr + - 0x0080: supports allocating child pty + - 0x0100 to 0x8000: (currently unused) + +currently uxn11 will write 0x00ff to the flags field, so programs +wishing to detect unx11 can check for that value. here's a snippet +that determines whether the console expansion can be used: + + |00 @System [ &unused $2 &expansion $2 ( ... ) ] + + |0010 + ;query .System/expansion DEO2 + ;query/flags LDA2 #00ff EQU2 ?enable-uxn11-console + BRK + + @enable-uxn-console ... + + @query 03 10 ( uuid ) 0123 1250 d878 4462 bc41 d092 7645 a2fa &flags 00 0000 + +note that uxn11 unconditionally enables the expanded console. so in +this case the uuid-based extension mechanism is only used to detect +the presence (or absence) of emulator support. if device 0x10 is not +provided then flags will be 0x0000 (moving or adding additional +console devices is not supported). diff --git a/src/devices/console.c b/src/devices/console.c index 75021c2..eb9c069 100644 --- a/src/devices/console.c +++ b/src/devices/console.c @@ -1,11 +1,17 @@ #undef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200112L +#ifdef __NetBSD__ +#define _NETBSD_SOURCE +#endif + +#include #include #include #include #include #include +#include #include #ifdef __linux @@ -14,6 +20,7 @@ #ifdef __NetBSD__ #include +#include #include #endif @@ -31,54 +38,293 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE. */ -/* process */ -static char *fork_args[32]; -static int child_mode; -static int pty_fd; -static int to_child_fd[2]; -static int from_child_fd[2]; -static int saved_in; -static int saved_out; -static pid_t child_pid; -struct winsize ws = {24, 80, 8, 12}; +#define child_ptys(mode) (mode & CONSOLE_MODE_PTY) +#define child_reads(mode) (mode & CONSOLE_CHILD_READS) +#define child_writes(mode) (mode & CONSOLE_CHILD_WRITES) +#define child_writes_out(mode) (mode & CONSOLE_MODE_OUT) +#define child_writes_err(mode) (mode & CONSOLE_MODE_ERR) -static void -parse_args(Uxn *u, Uint8 *d) +UxnSubprocess children[CONSOLE_MAX_CHILDREN]; +static char *fork_args[4] = {"/bin/sh", "-c", "", NULL}; +static int to_child_fd[2], from_child_fd[2]; +static char buf[16]; +static int raw_tty = 0; +struct termios old_termios; + +static UxnSubprocess* +find_child_by_pid(pid_t pid) { - Uint8 *port_addr = d + 0x3; - int addr = PEEK2(port_addr); - char *pos = (char *)&u->ram[addr]; - int i = 0; - do { - fork_args[i++] = pos; - while(*pos != 0) pos++; - pos++; - } while(*pos != '\0'); - fork_args[i] = NULL; + for(int n = 0; n < CONSOLE_MAX_CHILDREN; n++) + if(children[n].pid == pid) + return &children[n]; + return NULL; } -/* call after we're sure the process has exited */ static void -clean_after_child(void) +handle_sigchld(int sig) { - child_pid = 0; - if(child_mode >= 0x80) { - close(pty_fd); - dup2(saved_in, 0); - dup2(saved_out, 1); - } else { - if(child_mode & 0x01) { - close(to_child_fd[1]); - dup2(saved_out, 1); - } - if(child_mode & 0x06) { - close(from_child_fd[0]); - dup2(saved_in, 0); + pid_t pid; + int n, wstatus; + while((pid = waitpid(-1, &wstatus, WNOHANG)) > 0) { + UxnSubprocess *child = find_child_by_pid(pid); + if (child != NULL) { + /* set running to negative when exiting for clean up */ + child->running = WEXITSTATUS(wstatus) - 256; + /* close relevant file descriptors */ + if(child_ptys(child->mode)) + close(child->pty); + else { + if(child_reads(child->mode)) close(child->fd_in); + if(child_writes(child->mode)) close(child->fd_out); + } + /* reset fields to starting values */ + child->pid = child->mode = 0; + child->fd_out = child->fd_in = child->pty = -1; + return; + } + } +} + +static void +start_fork_pty(UxnSubprocess *child, int mode) +{ + int fd = -1; + struct winsize ws = {23, 80, 8, 12}; + pid_t pid = forkpty(&fd, NULL, NULL, &ws); + if(pid < 0) { /* failure */ + fprintf(stderr, "parent fork failure\n"); + } else if(pid == 0) { /* child */ + execvp(fork_args[0], fork_args); + fprintf(stderr, "child exec failure\n"); + } else { /*parent*/ + do { + child->pid = pid; + child->mode = mode; + child->running = 1; + child->pty = fd; + child->fd_in = fd; + child->fd_out = fd; + } while (child->pid != pid); + /* loop to avoid possible race with clean up */ + } +} + +static void +start_fork_pipe(UxnSubprocess *child, int mode) +{ + if(child_reads(mode)) { + /* parent writes to child's stdin */ + if(pipe(to_child_fd) == -1) { + fprintf(stderr, "pipe error: to child\n"); + return; + } + } + + if(child_writes(mode)) { + /* parent reads from child's stdout and/or stderr */ + if(pipe(from_child_fd) == -1) { + fprintf(stderr, "pipe error: from child\n"); + return; + } + } + + pid_t pid = fork(); + if(pid < 0) { /* failure */ + fprintf(stderr, "fork failure\n"); + return; + } else if(pid == 0) { /* child */ + if(child_reads(mode)) { + dup2(to_child_fd[0], 0); + close(to_child_fd[1]); + } + if(child_writes(mode)) { + if(child_writes_out(mode)) dup2(from_child_fd[1], 1); + if(child_writes_err(mode)) dup2(from_child_fd[1], 2); + close(from_child_fd[0]); + } + execvp(fork_args[0], fork_args); + fprintf(stderr, "child exec failure\n"); + exit(1); + } else { /*parent*/ + do { + child->pid = pid; + child->mode = mode; + child->running = 1; + child->pty = -1; + child->fd_in = child_reads(mode) ? to_child_fd[1] : -1; + child->fd_out = child_writes(mode) ? from_child_fd[0] : -1; + } while (child->pid != pid); + /* loop to avoid possible race with clean up */ + + /* close the unused end of pipes we opened */ + if(child_reads(mode)) close(to_child_fd[0]); + if(child_writes(mode)) close(from_child_fd[1]); + } +} + +static void +std_put(Uint8 c, FILE *fd) +{ + fputc(c, fd); + fflush(fd); +} + +static void +proc_put(Uxn *u, Uint8 *d) +{ + Uint8 c = d[0xa]; + int n = d[0xe] & 0x3; + write(children[n].fd_in, &c, 1); + fdatasync(children[n].fd_in); +} + +static void +host_execute(Uxn *u, Uint8 *d) +{ + int addr = PEEK2(d + 0xc); + int opts = d[0xe]; + int mode = opts & 0xf0; + char *cmd = (char *)&u->ram[addr]; + UxnSubprocess *child = &children[opts & 0x3]; + + fork_args[2] = cmd; + if(child->pid > 0) + kill(child->pid, 9); + + if(opts >= 0x80) + start_fork_pty(child, mode); + else + start_fork_pipe(child, mode); +} + +static void +host_response(Uxn *u, char *s) +{ + for(int i = 0;; i++) + console_input(u, s[i], CONSOLE_HOST); + console_input(u, '\0', CONSOLE_HOST_END); +} + +static void +host_getpid(Uxn *u, Uint8 *d) +{ + int n = d[0xe] & 0x3; + snprintf(buf, 16, "%d", children[n].pid); + host_response(u, buf); +} + +static void +host_kill(Uxn *u, Uint8 *d) +{ + int n = d[0xe] & 0x3; + if(children[n].pid > 0) + kill(children[n].pid, 15); +} + +static void +host_raw_tty() +{ + if(!raw_tty) { + tcgetattr(STDIN_FILENO, &old_termios); + struct termios term = old_termios; + /* corresponds to `stty raw -echo` */ + term.c_cflag |= (CS8); + term.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP); + term.c_iflag &= ~(INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY | IMAXBEL); + term.c_lflag &= ~(ICANON | ISIG | ECHO); + term.c_oflag &= ~(OPOST); + term.c_cc[VMIN] = 0; + term.c_cc[VTIME] = 1; + tcsetattr(0, TCSAFLUSH, &term); + raw_tty = 1; + } +} + +static void +host_restore_tty() +{ + if(raw_tty) { + tcsetattr(0, TCSAFLUSH, &old_termios); + raw_tty = 0; + } +} + +static void +host_getenv(Uxn *u, Uint8 *d) +{ + int addr = PEEK2(d + 0xc); + char *name = (char *)&u->ram[addr]; + host_response(u, getenv(name)); +} + +static void +host_setenv(Uxn *u, Uint8 *d) +{ + int addr = PEEK2(d + 0xc); + char *name = (char *)&u->ram[addr]; + int i = 0; + while(name[i] != '=') i++; + if(name[i] == '=') { + name[i] = '\0'; + char *value = &name[i+1]; + setenv(name, value, 1); + } else { + fprintf(stderr, "mal-formed setenv\n"); + } +} + +static void +host_put(Uxn *u, Uint8 *d) +{ + switch(d[0xf]) { + case 0x00: /* nop */ break; + case 0x01: host_execute(u, d); break; + case 0x02: host_getpid(u, d); break; + case 0x03: host_kill(u, d); break; + case 0x04: host_raw_tty(); break; + case 0x05: host_restore_tty(); break; + case 0x10: host_getenv(u, d); break; + case 0x11: host_setenv(u, d); break; + } +} + +void +init_console(void) +{ + /* set up signal handler for SIGCHLD */ + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = handle_sigchld; + sigaction(SIGCHLD, &sa, NULL); + + /* initialize (halted) children */ + for(int i = 0; i < CONSOLE_MAX_CHILDREN; i++) { + UxnSubprocess *child = &children[i]; + child->id = i; + child->pid = child->mode = child->running = 0; + child->fd_in = child->fd_out = child->pty = -1; + } +} + +UxnSubprocess +*get_child(int n) +{ + return &children[n & 0x3]; +} + +void +console_monitor(Uxn *u) +{ + /*fflush(stdout);*/ /* TODO: needed? stderr? children? */ + for(int n = 0; n < CONSOLE_MAX_CHILDREN; n++) { + UxnSubprocess *child = &children[n]; + if(child->running < 0) { + Uint8 status = child->running + 256; + child->running = 0; + console_input(u, status, CONSOLE_CHILD_EXIT | n); } } - child_mode = 0; - saved_in = -1; - saved_out = -1; } int @@ -96,137 +342,24 @@ console_listen(Uxn *u, int i, int argc, char **argv) for(; i < argc; i++) { char *p = argv[i]; while(*p) console_input(u, *p++, CONSOLE_ARG); - console_input(u, '\n', i == argc - 1 ? CONSOLE_END : CONSOLE_EOA); + console_input(u, '\n', i == argc - 1 ? CONSOLE_ARG_END : CONSOLE_ARG_SPACER); } } -static void -start_fork_pty(Uint8 *d) -{ - int fd = -1; - pid_t pid = forkpty(&fd, NULL, NULL, NULL); - if(pid < 0) { /* failure */ - d[0x6] = 0xff; - fprintf(stderr, "fork failure\n"); - } else if(pid == 0) { /* child */ - setenv("TERM", "ansi", 1); - execvp(fork_args[0], fork_args); - d[0x6] = 0xff; - fprintf(stderr, "exec failure\n"); - } else { /*parent*/ - child_pid = pid; - pty_fd = fd; - ioctl(fd, TIOCSWINSZ, &ws); - saved_in = dup(0); - saved_out = dup(1); - dup2(fd, 0); - dup2(fd, 1); - } -} - -static void -start_fork_pipe(Uint8 *d) -{ - if(child_mode & 0x01) { - /* parent writes to child's stdin */ - if(pipe(to_child_fd) == -1) { - d[0x6] = 0xff; - fprintf(stderr, "pipe error: to child\n"); - return; - } - } - - if(child_mode & 0x06) { - /* parent reads from child's stdout and/or stderr */ - if(pipe(from_child_fd) == -1) { - d[0x6] = 0xff; - fprintf(stderr, "pipe error: from child\n"); - return; - } - } - - pid_t pid = fork(); - if(pid < 0) { /* failure */ - d[0x6] = 0xff; - fprintf(stderr, "fork failure\n"); - } else if(pid == 0) { /* child */ - if(child_mode & 0x01) { - dup2(to_child_fd[0], 0); - close(to_child_fd[1]); - } - if(child_mode & 0x06) { - if(child_mode & 0x02) dup2(from_child_fd[1], 1); - if(child_mode & 0x04) dup2(from_child_fd[1], 2); - close(from_child_fd[0]); - } - execvp(fork_args[0], fork_args); - d[0x6] = 0xff; - fprintf(stderr, "exec failure\n"); - } else { /*parent*/ - child_pid = pid; - if(child_mode & 0x01) { - saved_out = dup(1); - dup2(to_child_fd[1], 1); - close(to_child_fd[0]); - } - if(child_mode & 0x06) { - saved_in = dup(0); - dup2(from_child_fd[0], 0); - close(from_child_fd[1]); - } - } -} - -static void -kill_child(Uint8 *d, int options) -{ - if(child_pid) { - kill(child_pid, 9); - int wstatus; - if(waitpid(child_pid, &wstatus, options)) { - d[0x6] = 1; - d[0x7] = WEXITSTATUS(wstatus); - clean_after_child(); - } - } -} - -static void -start_fork(Uxn *u, Uint8 *d) -{ - fflush(stderr); - kill_child(d, 0); - child_mode = d[0x5]; - parse_args(u, d); - if(child_mode >= 0x80) - start_fork_pty(d); - else - start_fork_pipe(d); -} - Uint8 console_dei(Uxn *u, Uint8 addr) { Uint8 port = addr & 0x0f, *d = &u->dev[addr & 0xf0]; - switch(port) { - case 0x6: - case 0x7: kill_child(d, WNOHANG); - } return d[port]; } void console_deo(Uxn *u, Uint8 *d, Uint8 port) { - FILE *fd = NULL; switch(port) { - case 0x5: /* Console/dead */ start_fork(u, d); break; - case 0x6: /* Console/exit*/ kill_child(d, 0); break; - case 0x8: fd = stdout; break; - case 0x9: fd = stderr; break; - } - if(fd) { - fputc(d[port], fd); - fflush(fd); + case 0x8: std_put(d[port], stdout); break; + case 0x9: std_put(d[port], stderr); break; + case 0xa: proc_put(u, d); break; + case 0xf: host_put(u, d); break; } } diff --git a/src/devices/console.h b/src/devices/console.h index 679b4e0..c9cb9a6 100644 --- a/src/devices/console.h +++ b/src/devices/console.h @@ -11,12 +11,47 @@ WITH REGARD TO THIS SOFTWARE. #define CONSOLE_VERSION 1 -#define CONSOLE_STD 0x1 -#define CONSOLE_ARG 0x2 -#define CONSOLE_EOA 0x3 -#define CONSOLE_END 0x4 +#define CONSOLE_NONE 0x00 +#define CONSOLE_STDIN 0x01 +#define CONSOLE_ARG 0x02 +#define CONSOLE_ARG_SPACER 0x03 +#define CONSOLE_ARG_END 0x04 +#define CONSOLE_HOST 0x05 +#define CONSOLE_HOST_END 0x06 +#define CONSOLE_STDIN_END 0x07 +#define CONSOLE_CHILD_DATA 0x20 /* write from child-n: 0x20 | n */ +#define CONSOLE_CHILD_END 0x40 /* close from child-n: 0x40 | n */ +#define CONSOLE_CHILD_EXIT 0x80 /* exits from child-n: 0x80 | n */ + +typedef struct UxnSubprocess { + int id; /* identifier: 0-3 */ + pid_t pid; /* when running, pid > 0 */ + int mode; /* bit flags: CONSOLE_MODE_ */ + int running; /* 0=stopped, >0 running, <0 exiting */ + int fd_in; /* fd to send to child; -1 is unset */ + int fd_out; /* fd to read from child; -1 is unset */ + int pty; /* pty for child; -1 is unset */ +} UxnSubprocess; + +void init_console(void); +UxnSubprocess *get_child(int n); +void console_monitor(Uxn *u); int console_input(Uxn *u, char c, int type); void console_listen(Uxn *u, int i, int argc, char **argv); Uint8 console_dei(Uxn *u, Uint8 addr); void console_deo(Uxn *u, Uint8 *d, Uint8 port); + +#define CONSOLE_MODE_PTY 0x80 +#define CONSOLE_MODE_ERR 0x40 +#define CONSOLE_MODE_OUT 0x20 +#define CONSOLE_MODE_INP 0x10 + +/* CONSOLE_MODE_PTY | CONSOLE_MODE_INP */ +#define CONSOLE_CHILD_READS 0x90 + +/* CONSOLE_MODE_PTY | CONSOLE_MODE_OUT | CONSOLE_MODE_ERR */ +#define CONSOLE_CHILD_WRITES 0xe0 + +#define CONSOLE_MAX_CHILDREN 4 + diff --git a/src/devices/system.c b/src/devices/system.c index 81521ca..e55e48f 100644 --- a/src/devices/system.c +++ b/src/devices/system.c @@ -100,6 +100,45 @@ system_boot(Uxn *u, Uint8 *ram, char *rom) return 1; } +static void +system_expansion_copy(Uxn *u, Uint8 *ram, Uint16 addr) +{ + Uint16 i, length = PEEK2(ram + addr + 1); + Uint16 a_page = PEEK2(ram + addr + 1 + 2), a_addr = PEEK2(ram + addr + 1 + 4); + Uint16 b_page = PEEK2(ram + addr + 1 + 6), b_addr = PEEK2(ram + addr + 1 + 8); + int src = (a_page % RAM_PAGES) * 0x10000, dst = (b_page % RAM_PAGES) * 0x10000; + for(i = 0; i < length; i++) + ram[dst + (Uint16)(b_addr + i)] = ram[src + (Uint16)(a_addr + i)]; +} + +static int +uuid_eq(Uint8 *m, Uint8 uuid[16]) +{ + for(int i = 0; i < 16; i++) + if (m[i] != uuid[i]) + return 0; + return 1; +} + +/* console device: 01231250-d878-4462-bc41-d0927645a2fa */ +static Uint8 console_uuid[16] = { + 0x01, 0x23, 0x12, 0x50, 0xd8, 0x78, 0x44, 0x62, + 0xbc, 0x41, 0xd0, 0x92, 0x76, 0x45, 0xa2, 0xfa +}; + +static void +system_expansion_uxn38_device(Uxn *u, Uint8 *ram, Uint16 addr) +{ + Uint8 dev_id = ram[addr + 1]; + Uint8 *uuid = &ram[addr + 2]; + if(uuid_eq(uuid, console_uuid) != 0) { + int ok = dev_id == 0x10 ? 0xff : 0x00; + ram[addr + 18] = ok; + ram[addr + 19] = 0x00; + ram[addr + 20] = ok; + } +} + /* IO */ Uint8 @@ -121,14 +160,10 @@ system_deo(Uxn *u, Uint8 *d, Uint8 port) case 0x3: ram = u->ram; addr = PEEK2(d + 2); - if(ram[addr] == 0x1) { - Uint16 i, length = PEEK2(ram + addr + 1); - Uint16 a_page = PEEK2(ram + addr + 1 + 2), a_addr = PEEK2(ram + addr + 1 + 4); - Uint16 b_page = PEEK2(ram + addr + 1 + 6), b_addr = PEEK2(ram + addr + 1 + 8); - int src = (a_page % RAM_PAGES) * 0x10000, dst = (b_page % RAM_PAGES) * 0x10000; - for(i = 0; i < length; i++) - ram[dst + (Uint16)(b_addr + i)] = ram[src + (Uint16)(a_addr + i)]; - } + if(ram[addr] == 0x1) + system_expansion_copy(u, ram, addr); + else if(ram[addr] == 0x3) + system_expansion_uxn38_device(u, ram, addr); break; case 0x4: u->wst.ptr = d[4]; diff --git a/src/uxn11.c b/src/uxn11.c index 6e91401..907537a 100644 --- a/src/uxn11.c +++ b/src/uxn11.c @@ -1,8 +1,10 @@ +#include #include #include #include #include #include +#include #include #include #include @@ -102,6 +104,7 @@ emu_end(Uxn *u) XDestroyImage(ximage); XDestroyWindow(display, window); XCloseDisplay(display); + kill(0, 15); exit(0); return u->dev[0x0f] & 0x7f; } @@ -131,6 +134,23 @@ toggle_scale(void) screen_resize(uxn_screen.width, uxn_screen.height, s); } +/* returns true if the fd ended (has been closed), false otherwise */ +static int +handle_input(Uxn *u, struct pollfd pollfd, char *coninp, int argdata, int argend) +{ + if((pollfd.revents & POLLIN) != 0) { + int n = read(pollfd.fd, coninp, CONINBUFSIZE - 1); + coninp[n] = 0; + if(n == 0) + console_input(u, 0, argend); + else + for(int i = 0; i < n; i++) + console_input(u, coninp[i], argdata); + return n == 0; + } else + return 0; +} + static void emu_event(Uxn *u) { @@ -200,6 +220,7 @@ display_init(void) if(!display) return system_error("init", "Display failed"); screen_resize(WIDTH, HEIGHT, 1); + init_console(); /* start window */ visual = DefaultVisual(display, 0); if(visual->class != TrueColor) @@ -224,9 +245,10 @@ static int emu_run(Uxn *u, char *rom) { int i = 1, n; + int *child_fd_outs; char expirations[8]; char coninp[CONINBUFSIZE]; - struct pollfd fds[3]; + struct pollfd fds[3 + CONSOLE_MAX_CHILDREN]; static const struct itimerspec screen_tspec = {{0, 16666666}, {0, 16666666}}; /* timer */ fds[0].fd = XConnectionNumber(display); @@ -236,7 +258,11 @@ emu_run(Uxn *u, char *rom) fds[0].events = fds[1].events = fds[2].events = POLLIN; /* main loop */ while(!u->dev[0x0f]) { - if(poll(fds, 3, 1000) <= 0) + for(i = 0; i < CONSOLE_MAX_CHILDREN; i++) { + fds[i + 3].fd = get_child(i)->fd_out; + fds[i + 3].events = POLLIN; + } + if(poll(fds, 3 + CONSOLE_MAX_CHILDREN, 1000) <= 0) continue; while(XPending(display)) emu_event(u); @@ -250,13 +276,18 @@ emu_run(Uxn *u, char *rom) XPutImage(display, window, DefaultGC(display, 0), ximage, x, y, x + PAD, y + PAD, w, h); } } - if((fds[2].revents & POLLIN) != 0) { - n = read(fds[2].fd, coninp, CONINBUFSIZE - 1); - coninp[n] = 0; - for(i = 0; i < n; i++) - console_input(u, coninp[i], CONSOLE_STD); - } + + /* read input from stdin */ + handle_input(u, fds[2], coninp, CONSOLE_STDIN, CONSOLE_STDIN_END); + + /* read input from child processes */ + for(i = 0; i < CONSOLE_MAX_CHILDREN; i++) + handle_input(u, fds[i + 3], coninp, CONSOLE_CHILD_DATA | i, CONSOLE_CHILD_END | i); + + /* check to see if any children exited */ + console_monitor(u); } + kill(0, 15); return 1; } diff --git a/src/uxncli.c b/src/uxncli.c index 950209a..72a8096 100644 --- a/src/uxncli.c +++ b/src/uxncli.c @@ -1,5 +1,8 @@ #include #include +#include +#include +#include #include "uxn.h" #include "devices/system.h" @@ -42,16 +45,59 @@ emu_deo(Uxn *u, Uint8 addr, Uint8 value) } } +static void +handle_input(Uxn *u, int fd, int port, int argdata, int argend) +{ + char buf[32]; + int n = read(fd, &buf, 32); + if(n == 0) + console_input(u, 0x00, argend); + else + for(int i = 0; i < n; i++) + console_input(u, buf[i], argdata); +} + +static int +init_rfds(fd_set *rfds) +{ + int i, nfds; + FD_ZERO(rfds); + FD_SET(0, rfds); + nfds = 1; + for(i = 0; i < 4; i++) { + UxnSubprocess *child = get_child(i); + int fd = child->fd_out; + if (fd >= 0) { + FD_SET(fd, rfds); + if (fd >= nfds) nfds = fd + 1; + } + } + return nfds; +} + static void emu_run(Uxn *u) { + int i; + struct timeval tv; + fd_set rfds; while(!u->dev[0x0f]) { - int c = fgetc(stdin); - if(c == EOF) { - console_input(u, 0x00, CONSOLE_END); - break; + int nfds = init_rfds(&rfds); + /* tv.tv_sec = 0; */ + tv.tv_sec = 0; + tv.tv_usec = 1000; + int retval = select(nfds, &rfds, NULL, NULL, &tv); + if(retval > 0) { + /* printf("got something!\n"); */ + if (FD_ISSET(0, &rfds)) + handle_input(u, 0, 0x2, CONSOLE_STDIN, CONSOLE_STDIN_END); + for(i = 0; i < 4; i++) { + int fd = get_child(i)->fd_out; + if (fd > 2 && FD_ISSET(fd, &rfds)) + handle_input(u, fd, 0x4, CONSOLE_CHILD_DATA | i, CONSOLE_CHILD_END | i); + } } - console_input(u, (Uint8)c, CONSOLE_STD); + console_monitor(u); } } @@ -76,6 +122,7 @@ main(int argc, char **argv) return system_error("Init", "Failed to initialize uxn."); /* Game Loop */ u.dev[0x17] = argc - i; + init_console(); if(uxn_eval(&u, PAGE_PROGRAM) && PEEK2(u.dev + 0x10)) { console_listen(&u, i, argc, argv); emu_run(&u);