Support serial devices and verbose logging

This change adds support for connecting to physical or virtual serial
ports with the "--device" flag. Additionally, the "--verbose" flag can
be used to log each character sent and received to stdout.

Man page has been updated.
This commit is contained in:
Seth Morabito 2022-08-31 11:16:47 -07:00
parent f9b8f47a8f
commit 2eed0f3623
3 changed files with 300 additions and 96 deletions

View File

@ -5,10 +5,11 @@ dmd5620 \- AT&T DMD 5620 Terminal Emulator
.B dmd5620
[\fB\--help\fR]
[\fB\--version\fR]
[\fB\--verbose\fR]
[\fB\--delete\fR]
[\fB\--shell\fR \fISHELL\fR]
[\fB\--shell\fR \fISHELL\fR|\fB\--device\fR \fIDEVICE\fR]
[\fB\--nvram\fR \fIFILE\fR]
.IR INPUT
[\fB\--trace\fR \fIFILE\fR]
.SH DESCRIPTION
.B dmd5620
AT&T DMD 5620 Terminal emulator with support for XT layers protocol.
@ -20,20 +21,26 @@ Displays help and exits.
.BR \-v ", " \-\-version
Displays version and exits.
.TP
.BR \-d ", " \-\-delete
.BR \-V ", " \-\-verbose
Print verbose logging to stdout.
.TP
.BR \-D ", " \-\-delete
Send a delete character ^? (0x7F hex) when the backspace key is pressed. By
default, ^H (0x08 hex) is sent.
.TP
.BR \-s ", " \-\-shell " " \fISHELL\fR
Execute the program \fISHELL\fR. By default, the user's default login shell is
executed.
Execute the program \fISHELL\fR, e.g. \"/bin/sh\". (Either
\fB\-\-shell\fR or \fB\-\-device\fR is required)
.TP
.BR \-d ", " \-\-device " " \fIDEVICE\fR
Connect to physical device \fIDEVICE\fR, e.g. "/dev/ttyS0" or
"/dev/pts/1".
.TP
.BR \-n ", " \-\-nvram " " \fIFILE\fR
Store NVRAM state in file \fIFILE\fR. No default.
.TP
.BR \-t ", " \-\-trace " " \fIFILE\fR
Log verbose system execution trace to \fIFILE\fR. This can be extremely
expensive, and should be used with care!
Allow logging verbose system execution trace to \fIFILE\fR.
.SH KEYMAP
.TP
.BR F1\-F8
@ -44,6 +51,27 @@ Terminal SETUP key
.TP
.BR SHIFT\+F9
Terminal RESET key
.TP
.BR F10
Enable or disable execution tracing, if the \fB\-\-trace\fR option has
been used.
.SH CONNECTING TO VIRTUAL SERIAL PORTS
The \fB\-\-device\fR option can be used with \fBsocat\fR to set up a
virtual serial port to a SIMH instance. For example:
.P
.EX
$ socat -d -d pty,raw,echo=0 TCP:192.168.0.100:8888
.EE
.P
This command on Linux will open a virtual serial port connected to
\fB192.168.80.100\fR on port \fB8888\fR. The port device will be
something like \fB/dev/pts/1\fR, and the \fB\-\-device\fR flag can
then be used to connect to this virtual serial port.
.SH TERMINAL EXECUTION TRACING
The \fB\-\-trace\fR option allows verbose tracing of WE32100 CPU
instructions to the specified file. Tracing is turned on or off by
pressing the \fBF10\fR key. \fITracing execution is extremely expensive
and should be used with care, it will degrade performance!\fR
.SH USING SVR3 LAYERS
One of the primary featuers of dmd5620 is support for the System V Release 3
\fIlayers\fR program. Real DMD5620 terminals were hard-wired directly

@ -1 +1 @@
Subproject commit dc4facf240537d733721a3f95bc0a990c1c7c4e5
Subproject commit c3677ab006318dad0ba85dc2f9c16f9636701755

View File

@ -25,6 +25,7 @@
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <poll.h>
#include <netdb.h>
@ -50,6 +51,7 @@
#include "dmd_5620.h"
#define PCHAR(p) (((p) >= 0x20 && (p) < 0x7f) ? (p) : '.')
#define TX_BUF_LEN 64
static char VERSION_STRING[64];
static GtkWidget *main_window;
@ -62,9 +64,13 @@ static uint8_t previous_vram[VIDRAM_SIZE];
static struct pollfd fds[2];
static pid_t shell_pid;
static int erase_is_delete = FALSE;
static int verbose = FALSE;
static volatile int window_beep = FALSE;
static volatile int dmd_thread_run = TRUE;
static int sigint_count = 0;
static const char *trace_file = NULL;
static int trace_on = FALSE;
static int tty_fd = -1;
static void
int_handler(int signal)
@ -243,6 +249,199 @@ mouse_button(GtkWidget *widget, GdkEventButton *event, gpointer data)
return TRUE;
}
static void
pty_init(const char *shell)
{
char pty_name[64];
/* Set up our PTY */
if (openpty(&pty_master, &pty_slave, pty_name, NULL, NULL) < 0) {
perror("Could not open terminal pty: ");
exit(-1);
}
/* Fork the shell process */
fds[0].fd = pty_master;
fds[0].events = POLLIN;
fds[1].fd = pty_slave;
fds[1].events = POLLOUT;
shell_pid = fork();
if (shell_pid < 0) {
perror("Could not fork child shell: ");
exit(-1);
} else if (shell_pid == 0) {
/* Child */
char *const env[] = {"TERM=dmd", NULL};
int retval;
close(pty_master);
setsid();
if (ioctl(pty_slave, TIOCSCTTY, NULL) == -1) {
perror("Ioctl erorr: ");
exit(-1);
}
dup2(pty_slave, 0);
dup2(pty_slave, 1);
dup2(pty_slave, 2);
close(pty_slave);
if (shell) {
retval = execle(shell, "-", NULL, env);
} else {
retval = execle("/bin/sh", "-", NULL, env);
}
/* Child process is now replaced, nothing beyond this point
will ever be reached unless there's an error. */
if (retval < 0) {
perror("Could not start shell process: ");
exit(-1);
}
}
close(pty_slave);
}
/*
* PTY implemntation of read and write polling
*/
static void
pty_io_poll()
{
uint8_t txc;
char tx_buf[TX_BUF_LEN];
int b_read, i;
if (poll(fds, 2, 100) > 0) {
if (fds[0].revents & POLLIN) {
b_read = read(pty_master, tx_buf, TX_BUF_LEN);
if (b_read <= 0) {
perror("Nothing to read from child: ");
exit(-1);
}
for (i = 0; i < b_read; i++) {
dmd_rx_char(tx_buf[i] & 0xff);
if (verbose) {
printf("<--- PTY rx_char[%02d]: %02x %c\n", i, tx_buf[i] & 0xff, PCHAR(tx_buf[i]));
}
}
}
} else {
fprintf(stderr, "Nothing to poll!!\n");
}
i = 0;
while (dmd_rs232_tx_poll(&txc) == 0) {
if (verbose) {
printf("---> PTY tx_char[%02d]: %02x %c\n", i++, txc & 0xff, PCHAR(txc));
}
if (write(pty_master, &txc, 1) < 0) {
fprintf(stderr, "Error %d from write: %s\n", errno, strerror(errno));
}
}
}
/*
* Open and initialize a TTY device (e.g. "/dev/ttyS0", "/dev/pts/1", etc.)
*/
static int
tty_init(int fd)
{
struct termios tty;
if (tcgetattr(fd, &tty) != 0) {
fprintf(stderr, "error %d from tcgetattr", errno);
return -1;
}
fds[0].fd = fd;
fds[0].events = POLLIN;
fds[1].fd = fd;
fds[1].events = POLLOUT;
cfsetospeed(&tty, B9600);
cfsetispeed(&tty, B9600);
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; /* 8-bit characters */
tty.c_iflag &= ~IGNBRK; /* No break */
tty.c_lflag = 0;
tty.c_oflag = 0;
tty.c_cc[VMIN] = 0;
tty.c_cc[VTIME] = 5;
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
tty.c_cflag |= (CLOCAL | CREAD);
tty.c_cflag &= ~(PARENB | PARODD);
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;
if (tcsetattr (fd, TCSANOW, &tty) != 0) {
fprintf(stderr, "error %d from tcsetattr", errno);
return -1;
}
return 0;
}
static void
tty_io_poll()
{
uint8_t txc;
char tx_buf[TX_BUF_LEN];
int b_read, i;
if (poll(fds, 2, 100) > 0) {
if (fds[0].revents & POLLIN) {
b_read = read(tty_fd, tx_buf, TX_BUF_LEN);
for (i = 0; i < b_read; i++) {
dmd_rx_char(tx_buf[i] & 0xff);
if (verbose) {
printf("<--- TTY rx_char[%02d]: %02x %c\n", i, tx_buf[i] & 0xff, PCHAR(tx_buf[i]));
}
}
}
}
i = 0;
while (dmd_rs232_tx_poll(&txc) == 0) {
if (verbose) {
printf("---> TTY tx_char[%02d]: %02x %c\n", i++, txc & 0xff, PCHAR(txc));
}
if (write(tty_fd, &txc, 1) < 0) {
fprintf(stderr, "error %d during write: %s\n", errno, strerror(errno));
}
}
}
static void
tty_set_blocking(int fd, int should_block)
{
struct termios tty;
memset (&tty, 0, sizeof tty);
if (tcgetattr (fd, &tty) != 0) {
fprintf(stderr, "error %d from tggetattr", errno);
return;
}
tty.c_cc[VMIN] = should_block ? 1 : 0;
tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout
if (tcsetattr (fd, TCSANOW, &tty) != 0) {
fprintf(stderr, "error %d setting term attributes", errno);
}
}
/*
* This is the main thread for stepping the DMD emulator.
*/
@ -251,14 +450,12 @@ dmd_cpu_thread(void *threadid)
{
struct timespec sleep_time_req, sleep_time_rem;
int size;
uint8_t txc, kbc;
uint8_t kbc;
uint8_t nvram_buf[NVRAM_SIZE];
int b_read, i;
FILE *fp;
char tx_buf[32];
sleep_time_req.tv_sec = 0;
sleep_time_req.tv_nsec = 100000;
sleep_time_req.tv_nsec = 500000;
dmd_reset();
@ -290,29 +487,12 @@ dmd_cpu_thread(void *threadid)
}
while (dmd_thread_run) {
dmd_step_loop(1000);
dmd_step_loop(5000);
if (poll(fds, 2, 100) > 0) {
if (fds[0].revents & POLLIN) {
b_read = read(pty_master, tx_buf, 32);
if (b_read <= 0) {
perror("Nothing to read from child: ");
exit(-1);
}
for (i = 0; i < b_read; i++) {
dmd_rx_char(tx_buf[i]);
}
}
if (dmd_rs232_tx_poll(&txc) == 0) {
if (write(pty_master, &txc, 1) < 0) {
perror("Child write failed: ");
/* Should we give up here? */
exit(-1);
}
}
if (tty_fd < 0) {
pty_io_poll();
} else {
tty_io_poll();
}
/* Poll for output for the keyboard */
@ -340,6 +520,7 @@ keydown(GtkWidget *widget, GdkEventKey *event, gpointer data)
{
gboolean is_ctrl = event->state & GDK_CONTROL_MASK;
gboolean is_shift = event->state & GDK_SHIFT_MASK;
int result;
uint8_t c = 0;
@ -377,6 +558,19 @@ keydown(GtkWidget *widget, GdkEventKey *event, gpointer data)
c = 0xae;
}
break;
case GDK_KEY_F10:
if (trace_file != NULL) {
if (trace_on) {
dmd_trace_off();
trace_on = FALSE;
} else {
if ((result = dmd_trace_on(trace_file))) {
fprintf(stderr, "Error: Cannot open trace file: %d\n", result);
}
trace_on = TRUE;
}
}
break;
case GDK_KEY_Escape:
c = 0x1b;
break;
@ -574,21 +768,25 @@ gtk_setup(int *argc, char ***argv)
static struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'},
{"delete", no_argument, 0, 'd'},
{"verbose", no_argument, 0, 'V'},
{"delete", no_argument, 0, 'D'},
{"shell", required_argument, 0, 's'},
{"trace", required_argument, 0, 't'},
{"device", required_argument, 0, 'd'},
{"nvram", required_argument, 0, 'n'},
{0, 0, 0, 0}};
void usage()
{
printf("Usage: dmd5620 [-h] [-v] [-d] [-t FILE] [-s SHELL] \\\n"
" [-n FILE] [-- <gtk_options> ...]\n");
printf("Usage: dmd5620 [-h] [-v] [-V] [-D] [-d DEV|-s SHELL]\\\n"
" [-t FILE] [-n FILE] [-- <gtk_options> ...]\n");
printf("AT&T DMD 5620 Terminal emulator.\n\n");
printf("-h, --help display help and exit.\n");
printf("-v, --version display version and exit.\n");
printf("-d, --delete backspace sends ^? (DEL) instead of ^H\n");
printf("-h, --help display help and exit\n");
printf("-v, --version display version and exit\n");
printf("-V, --verbose display verbose output\n");
printf("-D, --delete backspace sends ^? (DEL) instead of ^H\n");
printf("-t, --trace FILE trace to FILE\n");
printf("-d, --device DEV serial port name\n");
printf("-s, --shell SHELL execute SHELL instead of default user shell\n");
printf("-n, --nvram FILE store nvram state in FILE\n");
}
@ -599,9 +797,9 @@ main(int argc, char *argv[])
int c, errflg = 0;
long thread_id = 0;
int rs;
char pty_name[64];
char *user_shell = getenv("SHELL");
char *trace_file = NULL;
char *shell = NULL;
char *device = NULL;
struct stat sb;
snprintf(VERSION_STRING, 64, "%d.%d.%d",
VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD);
@ -614,7 +812,7 @@ main(int argc, char *argv[])
int option_index = 0;
while ((c = getopt_long(argc, argv, "vdh:n:t:s:",
while ((c = getopt_long(argc, argv, "vVdh:n:t:p:s:",
long_options, &option_index)) != -1) {
switch(c) {
case 0:
@ -628,15 +826,21 @@ main(int argc, char *argv[])
case 'v':
printf("Version: %s\n", VERSION_STRING);
exit(0);
case 'V':
verbose = TRUE;
break;
case 'n':
nvram = optarg;
break;
case 's':
user_shell = optarg;
shell = optarg;
break;
case 't':
trace_file = optarg;
break;
case 'p':
device = optarg;
break;
case '?':
fprintf(stderr, "Unrecognized option: -%c\n", optopt);
errflg++;
@ -649,64 +853,36 @@ main(int argc, char *argv[])
exit(1);
}
/* Set up our PTY */
if (openpty(&pty_master, &pty_slave, pty_name, NULL, NULL) < 0) {
perror("Could not open terminal pty: ");
exit(-1);
if (shell == NULL && device == NULL) {
fprintf(stderr, "Either --shell or --device is required.\n");
exit(1);
}
/* Fork the shell process */
fds[0].fd = pty_master;
fds[0].events = POLLIN;
fds[1].fd = pty_slave;
fds[1].events = POLLOUT;
shell_pid = fork();
if (shell_pid < 0) {
perror("Could not fork child shell: ");
exit(-1);
} else if (shell_pid == 0) {
/* Child */
char *const env[] = {"TERM=dmd", NULL};
int retval;
close(pty_master);
setsid();
if (ioctl(pty_slave, TIOCSCTTY, NULL) == -1) {
perror("Ioctl erorr: ");
exit(-1);
}
dup2(pty_slave, 0);
dup2(pty_slave, 1);
dup2(pty_slave, 2);
close(pty_slave);
if (user_shell) {
retval = execle(user_shell, "-", NULL, env);
} else {
retval = execle("/bin/sh", "-", NULL, env);
}
/* Child process is now replaced, nothing beyond this point
will ever be reached unless there's an error. */
if (retval < 0) {
perror("Could not start shell process: ");
exit(-1);
}
if (shell != NULL && device != NULL) {
fprintf(stderr, "Cannot specify both --shell or --device. Only one is allowed.\n");
exit(1);
}
if (trace_file != NULL) {
int result;
if ((result = dmd_trace_on(trace_file))) {
fprintf(stderr, "Error: Cannot open trace file: %d\n", result);
if (device == NULL) {
if (stat(shell, &sb) != 0 || (sb.st_mode & S_IXUSR) == 0) {
fprintf(stderr, "Cannot open %s as shell, or file is not executable.\n", shell);
exit(1);
}
pty_init(shell);
} else {
if (stat(device, &sb) != 0) {
fprintf(stderr, "Cannot open device %s.\n", device);
exit(1);
}
tty_fd = open(device, O_RDWR|O_NOCTTY|O_SYNC);
if (tty_fd < 0) {
fprintf(stderr, "error %d opening %s: %s\n", errno, device, strerror(errno));
return -1;
}
tty_init(tty_fd);
tty_set_blocking(tty_fd, 0); /* No blocking */
}
close(pty_slave);
/* Set up the GTK app */
gtk_setup(&argc, &argv);