1
0
mirror of synced 2026-01-13 23:27:31 +00:00

Compare commits

...

16 Commits

Author SHA1 Message Date
AK6DN
aadbc08fa9 updated README about serial card configuration 2025-02-17 22:15:27 -08:00
AK6DN
afb70ffab6 Change serial port iflag from IGNPAR to INPCK per bobaboba 11/83 debug 2018-04-03 12:56:32 -07:00
AK6DN
5a83ea113c Check setreuid() return value to eliminate warnings. Update makefile 2017-10-15 21:15:17 -07:00
AK6DN
ee01f9eefd Updated documentation test table 2017-05-20 13:54:18 -07:00
AK6DN
354bb43265 Updated documentation test table 2017-05-20 13:51:13 -07:00
AK6DN
e85a7746c0 Major serial comm rewrite to fully support handling BREAK condition 2017-05-20 13:35:36 -07:00
AK6DN
b98daf25a9 Updated .gitignore 2017-05-16 20:49:51 -07:00
AK6DN
4227a06e4c Reenable monitor() thread. 2017-05-16 20:47:40 -07:00
AK6DN
fff577b6ed Minor update to some debug printout in tu58drive.c 2017-05-16 20:05:01 -07:00
AK6DN
4abf650805 Added compiled cygwin exe with fileseek() update 2017-05-16 16:34:31 -07:00
AK6DN
d5775dd678 Fix minor bug in fileseek() routine when seeking to block 512. 2017-05-16 15:56:09 -07:00
AK6DN
e707b2bd9a Restructure baud rate table with ifdefs; update documentation 2017-05-07 13:06:16 -07:00
AK6DN
ba6692a67f Documentation update. 2017-01-10 16:03:12 -08:00
AK6DN
a83cdf8510 Add .gitignore file 2017-01-09 16:22:06 -08:00
AK6DN
115b9f06bb Added compiled cygwin v1.4o 2017-01-09 15:00:20 -08:00
AK6DN
d7fea97432 Updated serial.c baud rate setting to use cfsetispeed()/cfsetospeed() routines.
Changed serial stop bits default to 1 (from 2); added option switch to select 1 or 2.
2017-01-09 14:56:15 -08:00
10 changed files with 370 additions and 289 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# ignore these files
*~
*.o
*.exe
*.dsk
*.DSK
# the end

View File

@ -1,14 +1,38 @@
<B>tu58em</B> is a software emulation of a DECtapeII TU-58 block addressable cartridge tape drive. It requires only a standard Windows PC as a host with an RS232 serial port to connect to the target system.
<B>tu58em</B> is a software emulation of a DECtapeII TU-58 block addressable cartridge tape drive. It requires only a standard Windows PC as a host with a (real or USB) RS232 serial comm port to connect to the target system.
<B>tu58em</B> was originally based on the 1984 Dan Ts'o tu58 program, but has been almost completely rewritten to make it compile error free, improve the program flow, and add new functionality. It has been compiled within the CYGWIN environment, and will run either within a CYGWIN window or an MSDOS command window with the associated cygwin1.dll helper file. <B>tu58em</B> has been tested under Win2K, WinXPsp3, and Win7sp1. <B>tu58em</B> has compiled error free under Linux (Ubuntu 12.02LTS) but has not yet been rigorously tested in that environment.
<B>tu58em</B> was originally based on the 1984 Dan Ts'o tu58 program, but has been almost completely rewritten to make it compile error free, improve the program flow, and add new functionality. It has been compiled within the CYGWIN environment, and will run either within a CYGWIN window or an MSDOS command window with the associated cygwin1.dll helper file. <B>tu58em</B> has been tested under WinXPsp3, Win7sp1(64b), and Linux (Ubuntu 12.02LTS).
Each emulated .dsk image file is exactly 256KB (512 blocks of 512 bytes) of data and is a byte-for-byte image of the data on a TU-58 cartridge tape. As currently configured <B>tu58em</B> will support up to 8 drives per controller as DD0: to DD7: (altho this is easily changed in the source). <B>tu58em</B> will support disk image files as large as the TU58 protocol allows (ie, 32MB, or 64K 512B blocks); however most standard DEC operating system drivers restrict TU58 drives to 256KB each. The DEC driver must be patched to allow for larger than 256KB disk images.
<B>tu58em</B> has been tested using both native RS232 serial COM ports and serial ports emulated thru USB serial adapters.
As of v1.4m Mark Blair's updates for VAX mode operation (for VAX-730 microcode boot support) and background mode are integrated.
Note: <B>tu58em</B> is compiled using the CYGWIN serial interface library routines (ie, termios.h) and is the preferred version that should be used. <B>tu58ew</B> bypasses the CYGWIN serial interface layer and makes direct Windows serial comm routine calls. This version was done early on because of deficiencies in the CYGWIN serial library. However, the required features are now present so in reality <B>tu58ew</B> should be considered deprecated.
As of v2.0a the serial support routines have been rewritten to, under Linux, use termios.h PARMRK mode to allow detecting BREAK inline within the rx byte stream, and respond correctly (ie, abort current command in process and return to init loop). PARMRK mode in CYGWIN termios appears not be be working correctly at all. Windows communication mode (-DWINCOMM) is preferred for CYGWIN, as BREAK is detected correctly. MACOS support of PARMRK mode is unknown. The makefile is setup to detect the operating system type and define the compilation options correctly.
The latest version (as of 1.4m) integrates Mark Blair's updates for VAX mode operation (for VAX-730 microcode boot support) and background mode.
The following configurations have been tested:
```
System Mode Port Status
---------- -------------- ------------ --------------------------------------
Win/CYGWIN WINCOMM real comm PASS, BREAK detected
Win/CYGWIN WINCOMM USB ftdi PASS, BREAK detected
Win/CYGWIN WINCOMM USB prolific conditional PASS, BREAK not detected
---------- -------------- ------------ --------------------------------------
Win/CYGWIN termios real comm conditional PASS; BREAK not detected
Win/CYGWIN termios USB ftdi conditional PASS; BREAK not detected
Win/CYGWIN termios USB prolific conditional PASS; BREAK not detected
---------- -------------- ------------ --------------------------------------
Win/CYGWIN termios+PARMRK any FAIL, does not work at all
---------- -------------- ------------ --------------------------------------
Linux termios+PARMRK real comm PASS, BREAK detected
Linux termios+PARMRK USB ftdi PASS, BREAK detected
Linux termios+PARMRK USB prolific conditional PASS; BREAK not detected
---------- -------------- ------------ --------------------------------------
Note:
(1) Win/CYGWIN = CYGWIN_NT-5.1 1.7.35(0.287/5/3) i686 Cygwin
(2) Linux = Ubuntu 12.04.5 LTS (GNU/Linux 3.2.0-124-generic) x86_64 GNU/Linux
```
So it appears that the drivers for real comm ports handle BREAK correctly, as does the driver for the USB ftdi adapter, on both WinXP and Linux. The USB prolific adapter hardware and/or driver do not handle a BREAK, altho all data transfers operate correctly.
Configuring your serial card in the PDP-11 requires it be setup as 8-N-1 (8b data, no parity, one stop) AND that the serial interface card (example, a DL11-W in a UNIBUS system) be setup at the standard address of 776500 thru 776506. Also make sure that the card is enabled to send BREAK as that is an integral part of the TU58 serial protocol.
A cygwin folder with a precompiled 32b cygwin executable (tu58em.exe) is included for those without cygwin environment access. Under Windows, just open a standard CMD.EXE window, change to the cygwin folder, and run the <B>tu58em.exe</B> executable as a command line program.
@ -18,7 +42,7 @@ If the emulator is run with no options, it prints a usage screen:
E:\DEC> tu58em
ERROR: no units were specified
FATAL: illegal command line
tu58 tape emulator v1.4m
tu58 tape emulator v2.0a
Usage: ./tu58em [-options] -[rwci] file1 ... -[rwci] file7
Options: -V | --version output version string
-v | --verbose enable verbose output to terminal
@ -29,7 +53,8 @@ FATAL: illegal command line
-b | --background run in background mode, no console I/O except errors
-t | --timing 1 add timing delays to spoof diagnostic into passing
-T | --timing 2 add timing delays to mimic a real TU58
-s | --speed BAUD set line speed [1200..3000000; default 9600]
-s | --speed BAUD set line speed to BAUD; default 9600
-S | --stop BITS set stop bits 1..2; default 1
-p | --port PORT set port to PORT [1..N or /dev/comN; default 1]
-r | --read|rd FILENAME readonly drive
-w | --write FILENAME read/write drive
@ -51,10 +76,12 @@ Most of the switches should be pretty obvious:
-b run in background mode, no console I/O except errors
-t adds time delays to allow the emulator to pass the DEC ZTUUF0 TU-58 Performance Exerciser diagnostic
-T adds time delays to make the emulator nearly as slow as a real TU-58 (just for fun)
-s BAUD sets the baud rate; the following rates are supported. the default will be 9600 if not set.
3000000, 2500000, 2000000, 1500000, 1152000, 1000000, 921600, 576000, 500000,
460800, 256000, 230400, 115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200
-s BAUD sets the baud rate; the following rates may be supported. the default will be 9600 if not set.
3000000, 2500000, 2000000, 1500000, 1152000, 1000000, 921600, 576000, 500000,
460800, 230400, 115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200
exact list of baud rate support is system dependent (especially for rates above 230400)
-p PORT sets the com port as a number (1,2,3,...) or if not numeric the full path (/dev/com1)
-S STOP sets the number of stop bits (1 or 2), default is 1
-r FILENAME set the next unit as a read only drive using file FILENAME
-w FILENAME set the next unit as a read/write drive using file FILENAME
-c FILENAME set the next unit as a read/write drive using file FILENAME, zero the file before use
@ -69,10 +96,11 @@ E:\DEC> tu58em -p 3 -s 38400 -r boot.dsk -i rt11.dsk
info: initialize RT-11 directory on 'rt11.dsk'
info: unit 0 r file 'boot.dsk'
info: unit 1 rwci file 'rt11.dsk'
info: serial port 3 at 38400 baud
info: TU58 emulation start
info: serial port 3 at 38400 baud 1 stop
info: TU58 start
info: R restart, S toggle send init, V toggle verbose, D toggle debug, Q quit
info: emulator started
info: TU58 emulator started
info: <BREAK> seen
info: <INIT><INIT> seen, sending <CONT>
info: read unit=0 sw=0x00 mod=0x00 blk=0x0000 cnt=0x0400
info: read unit=0 sw=0x00 mod=0x00 blk=0x0006 cnt=0x0400

View File

@ -2,7 +2,7 @@
// tu58 - Emulate a TU58 over a serial line
//
// Original (C) 1984 Dan Ts'o <Rockefeller Univ. Dept. of Neurobiology>
// Update (C) 2005-2016 Donald N North <ak6dn_at_mindspring_dot_com>
// Update (C) 2005-2017 Donald N North <ak6dn_at_mindspring_dot_com>
//
// All rights reserved.
//
@ -80,10 +80,9 @@ typedef struct timespec timespec_t;
#define FILERT11INIT 4 // file should be init'ed as RT11 structure
#define FILEXXDPINIT 5 // file should be init'ed as XXDP structure
#define DEV_NYI -1 // not yet implemented
#define DEV_OK 0 // no error
#define DEV_BREAK 1 // BREAK on line
#define DEV_ERROR 2 // ERROR on line
#define DEV_NORMAL 0 // normal data byte
#define DEV_BREAK 1 // BREAK on line
#define DEV_ERROR 2 // ERROR on byte
@ -104,9 +103,8 @@ void devtxput (uint8_t);
int32_t devtxwrite (uint8_t *, int32_t);
void devrxinit (void);
int32_t devrxavail (void);
int32_t devrxerror (void);
uint8_t devrxget (void);
void devinit (char *, int32_t);
uint8_t devrxget (uint8_t *);
void devinit (char *, int32_t, int32_t);
void devrestore (void);
void coninit (void);
void conrestore (void);

Binary file not shown.

Binary file not shown.

2
file.c
View File

@ -354,7 +354,7 @@ int32_t fileseek (int32_t unit,
{
if (fileunit(unit)) return -1;
if (block*size+offset > lseek(file[unit].fd, 0, SEEK_END)) return -2;
if (block*size+offset >= lseek(file[unit].fd, 0, SEEK_END)) return -2;
if (lseek(file[unit].fd, block*size+offset, SEEK_SET) < 0) return -3;

69
main.c
View File

@ -2,7 +2,7 @@
// tu58 - Emulate a TU58 over a serial line
//
// Original (C) 1984 Dan Ts'o <Rockefeller Univ. Dept. of Neurobiology>
// Update (C) 2005-2016 Donald N North <ak6dn_at_mindspring_dot_com>
// Update (C) 2005-2017 Donald N North <ak6dn_at_mindspring_dot_com>
//
// All rights reserved.
//
@ -73,6 +73,17 @@
// v1.4m - 23 Feb 2016 - donorth - Added M. Blair's vax console timeout changes for '730
// - Added M. Blair's background mode option (no status).
// v1.4n - 06 Apr 2016 - donorth - Added capability for baud rates >230K, up to 3M
// v1.4o - 09 Jan 2017 - donorth - Removed baud rate 256000, it is nonstandard for unix.
// Changed serial setup to use cfsetispeed()/cfsetospeed()
// Added capability for 1 or 2 stop bits; default is 1
// v1.4p - 05 May 2017 - donorth - Updated serial baud rate table with #ifdef detection
// Update clock_gettime() for MAC OSX support
// v1.4q - 16 May 2017 - donorth - Fixed error in fileseek() routine (should fail at EOF)
// v2.0a - 19 May 2017 - donorth - Rewrite of serial.c to support PARMRK mode on linux
// so that BREAKs are handled correctly via setjmp/longjmp
// Update tu58drive.c to intercept rx byte read routine to
// detect input line BREAK and process as required.
// v2.0b - 03 Apr 2018 - donorth - Change iflags from PARMRK|IGNPAR to PARMRK|INPCK
//
@ -80,13 +91,14 @@
#include <getopt.h>
static char copyright[] = "(C) 2005-2016 Don North <ak6dn" "@" "mindspring.com>, " \
static char copyright[] = "(C) 2005-2017 Don North <ak6dn" "@" "mindspring.com>, " \
"(C) 1984 Dan Ts'o <Rockefeller University>";
static char version[] = "tu58 tape emulator v1.4n";
static char version[] = "tu58 tape emulator v2.0b";
static char port[32] = "1"; // default port number (COM1, /dev/ttyS0)
static long speed = 9600; // default line speed
static long stop = 1; // default stop bits, 1 or 2
uint8_t verbose = 0; // set nonzero to output more info
uint8_t timing = 0; // set nonzero to add timing delays
@ -152,7 +164,7 @@ void fatal (char *fmt, ...)
// main program
//
int main (int argc,
char **argv)
char *argv[])
{
long i;
long n = 0;
@ -160,37 +172,39 @@ int main (int argc,
// switch options
int opt_index = 0;
char opt_short[] = "dvVmnxbTtp:s:r:w:c:i:z:";
char opt_short[] = "dvVmnxbTtp:s:r:w:c:i:z:S:";
static struct option opt_long[] = {
{ "debug", no_argument, 0, 'd' },
{ "verbose", no_argument, 0, 'v' },
{ "version", no_argument, 0, 'V' },
{ "mrsp", no_argument, 0, 'm' },
{ "nosync", no_argument, 0, 'n' },
{ "vax", no_argument, 0, 'x' },
{ "background", no_argument, 0, 'b' },
{ "timing", required_argument, 0, -2 },
{ "port", required_argument, 0, 'p' },
{ "baud", required_argument, 0, 's' },
{ "speed", required_argument, 0, 's' },
{ "rd", required_argument, 0, 'r' },
{ "read", required_argument, 0, 'r' },
{ "write", required_argument, 0, 'w' },
{ "create", required_argument, 0, 'c' },
{ "initrt11", required_argument, 0, 'i' },
{ "initxxdp", required_argument, 0, 'z' },
{ 0, no_argument, 0, 0 }
{ "debug", no_argument, NULL, 'd' },
{ "verbose", no_argument, NULL, 'v' },
{ "version", no_argument, NULL, 'V' },
{ "mrsp", no_argument, NULL, 'm' },
{ "nosync", no_argument, NULL, 'n' },
{ "vax", no_argument, NULL, 'x' },
{ "background", no_argument, NULL, 'b' },
{ "timing", required_argument, NULL, -2 },
{ "port", required_argument, NULL, 'p' },
{ "baud", required_argument, NULL, 's' },
{ "speed", required_argument, NULL, 's' },
{ "stop", required_argument, NULL, 'S' },
{ "rd", required_argument, NULL, 'r' },
{ "read", required_argument, NULL, 'r' },
{ "write", required_argument, NULL, 'w' },
{ "create", required_argument, NULL, 'c' },
{ "initrt11", required_argument, NULL, 'i' },
{ "initxxdp", required_argument, NULL, 'z' },
{ NULL, no_argument, NULL, 0 }
};
// init file structures
fileinit();
// process command line options
while ((i = getopt_long(argc, argv, opt_short, opt_long, &opt_index)) != EOF) {
while ((i = getopt_long(argc, argv, opt_short, opt_long, &opt_index)) != -1) {
switch (i) {
case -2 : timing = atoi(optarg); if (timing > 2) errors++; break;
case 'p': strcpy(port, optarg); break;
case 's': speed = atoi(optarg); break;
case 'S': stop = atoi(optarg); break;
case 'r': fileopen(optarg, FILEREAD); n++; break;
case 'w': fileopen(optarg, FILEWRITE); n++; break;
case 'c': fileopen(optarg, FILECREATE); n++; break;
@ -232,7 +246,8 @@ int main (int argc,
" -b | --background run in background mode, no console I/O except errors\n" \
" -t | --timing 1 add timing delays to spoof diagnostic into passing\n" \
" -T | --timing 2 add timing delays to mimic a real TU58\n" \
" -s | --speed BAUD set line speed [1200..3000000; default 9600]\n" \
" -s | --speed BAUD set line speed to BAUD; default 9600\n" \
" -S | --stop BITS set stop bits 1..2; default 1\n" \
" -p | --port PORT set port to PORT [1..N or /dev/comN; default 1]\n" \
" -r | --read|rd FILENAME readonly drive\n" \
" -w | --write FILENAME read/write drive\n" \
@ -242,11 +257,11 @@ int main (int argc,
version, argv[0], NTU58-1);
// give some info
info("serial port %s at %d baud", port, speed);
info("serial port %s at %d baud %d stop", port, speed, stop);
if (mrspen) info("MRSP mode enabled (NOT fully tested - use with caution)");
// setup serial and console ports
devinit(port, speed);
devinit(port, speed, stop);
coninit();
// play TU58

View File

@ -2,50 +2,47 @@
# tu58em emulator makefile
#
ifeq ($(comm),mac)
# UNIX comms model, but on a Mac
PROG = tu58em
COMM = -UWINCOMM -DMACOSX
else ifeq ($(comm),win)
# WINDOWS comms model
PROG = tu58ew
COMM = -DWINCOMM
else # ifeq ($(comm),unix)
# UNIX comms model
PROG = tu58em
COMM = -UWINCOMM
# get operating system name
OPSYS = $(shell uname -s)
ifeq ($(OPSYS),Darwin)
# mac: UNIX comms model, but on MACOSX
OPTIONS = -DMACOSX
LFLAGS = -lpthread
BINDIR = /usr/local/bin
else ifeq ($(OPSYS:CYGWIN%=CYGWIN),CYGWIN)
# win: WINDOWS comms model under CYGWIN (any version)
OPTIONS = -DCYGWIN -DWINCOMM
LFLAGS = -lpthread -lrt
BINDIR = /cygdrive/e/DEC/tools/exe
else ifeq ($(OPSYS),Linux)
# unix: UNIX comms model under LINUX, use PARMRK serial mode
OPTIONS = -DLINUX -DUSE_PARMRK
LFLAGS = -lpthread -lrt
BINDIR = /usr/local/bin
else # unknown environment
OPTIONS =
LFLAGS = -lpthread -lrt
BINDIR = /usr/local/bin
endif
# put your binary installation directory here...
BIN = /cygdrive/e/DEC/tools/exe
# default program name, redefine PROG=xxx on command line if wanted
PROG = tu58em
# compiler flags and libraries
CC = gcc
CFLAGS = -I. -O3 -Wall -c $(COMM)
ifeq ($(comm),mac)
LFLAGS = -lpthread
else
LFLAGS = -lpthread -lrt
endif
CFLAGS = -I. -O3 -Wall -c $(OPTIONS)
$(PROG) : main.o tu58drive.o file.o serial.o
$(CC) -o $@ main.o tu58drive.o file.o serial.o $(LFLAGS)
all :
make --always comm=win
make clean
make --always comm=unix
make clean
mac :
make --always comm=mac
make --clean
installall :
make --always comm=win install
make clean
make --always comm=unix install
make clean
config :
@echo " OPSYS = \"$(OPSYS)\""
@echo " PROG = \"$(PROG)\""
@echo " BINDIR = \"$(BINDIR)\""
@echo " CC = \"$(CC)\""
@echo " CFLAGS = \"$(CFLAGS)\""
@echo " LFLAGS = \"$(LFLAGS)\""
clean :
-rm -f *.o
@ -57,7 +54,7 @@ purge : clean
-rm -f $(PROG) $(PROG).exe
install : $(PROG)
[ -d $(BIN) ] && cp $< $(BIN)
[ -d $(BINDIR) ] && cp $< $(BINDIR)
serial.o : serial.c common.h
$(CC) $(CFLAGS) serial.c

336
serial.c
View File

@ -2,7 +2,7 @@
// tu58 - Emulate a TU58 over a serial line
//
// Original (C) 1984 Dan Ts'o <Rockefeller Univ. Dept. of Neurobiology>
// Update (C) 2005-2016 Donald N North <ak6dn_at_mindspring_dot_com>
// Update (C) 2005-2017 Donald N North <ak6dn_at_mindspring_dot_com>
//
// All rights reserved.
//
@ -56,21 +56,21 @@
#define IUCLC 0 // Not POSIX
#define OLCUC 0 // Not POSIX
#define CBAUD 0 // Not POSIX
#endif
#endif // MACOSX
#include <termios.h>
#define BUFSIZE 256 // size of serial line buffers (bytes, each way)
// serial output buffer
static uint8_t wbuf[BUFSIZE];
static uint8_t wbuf[BUFSIZE];
static uint8_t *wptr;
static int32_t wcnt;
static int32_t wcnt;
// serial input buffer
static uint8_t rbuf[BUFSIZE];
static uint8_t rbuf[BUFSIZE];
static uint8_t *rptr;
static int32_t rcnt;
static int32_t rcnt;
#ifdef WINCOMM
// serial device descriptor, default to nada
@ -78,6 +78,7 @@ static HANDLE hDevice = INVALID_HANDLE_VALUE;
// async line parameters
static DCB dcbSave;
static COMMTIMEOUTS ctoSave;
static uint8_t rxBreakSeen;
#else // !WINCOMM
// serial device descriptor, default to nada
static int32_t device = -1;
@ -197,6 +198,7 @@ void devrxinit (void)
#ifdef WINCOMM
if (!PurgeComm(hDevice, PURGE_RXABORT|PURGE_RXCLEAR))
error("devrxinit(): error=%d", GetLastError());
rxBreakSeen = 0;
#else // !WINCOMM
tcflush(device, TCIFLUSH);
#endif // !WINCOMM
@ -211,75 +213,26 @@ void devrxinit (void)
//
// wait for an error on the serial line
// return NYI, OK, BREAK, ERROR flag
//
int32_t devrxerror (void)
{
#ifdef WINCOMM
// enable BREAK and ERROR events
OVERLAPPED ovlp = { 0 };
DWORD sts = 0;
if (!SetCommMask(hDevice, EV_BREAK|EV_ERR)) {
DWORD err = GetLastError();
if (err != ERROR_OPERATION_ABORTED)
error("devrxerror(): SetCommMask() failed, error=%d", err);
}
// do the status check
ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!WaitCommEvent(hDevice, &sts, &ovlp)) {
DWORD err = GetLastError();
if (err == ERROR_IO_PENDING) {
if (WaitForSingleObject(ovlp.hEvent, INFINITE) == WAIT_OBJECT_0)
GetOverlappedResult(hDevice, &ovlp, &sts, FALSE);
} else {
if (err != ERROR_OPERATION_ABORTED)
error("devrxerror(): WaitCommEvent() failed, error=%d", err);
}
}
// done
CloseHandle(ovlp.hEvent);
// indicate either a break or some other error or OK
return (sts & (CE_BREAK|CE_FRAME)) ? DEV_BREAK : (sts ? DEV_ERROR : DEV_OK);
#else // !WINCOMM
// not implemented
return DEV_NYI;
#endif // !WINCOMM
}
//
// return number of characters available
// return number of characters available, get more if receive buffer is empty
//
int32_t devrxavail (void)
{
// get more characters if none available
if (rcnt <= 0) {
#ifdef WINCOMM
OVERLAPPED ovlp = { 0 };
COMSTAT stat;
DWORD acnt = 0;
DWORD ncnt = 0;
DWORD sts = 0;
// clear state
if (!ClearCommError(hDevice, &sts, &stat))
error("devrxavail(): ClearCommError() failed");
if (debug && (sts || stat.cbInQue))
info("devrxavail(): status=0x%04X avail=%d", sts, stat.cbInQue);
// do the read if something there
if (stat.cbInQue > 0) {
ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!ReadFile(hDevice, rbuf, sizeof(rbuf), &acnt, &ovlp)) {
DWORD err = GetLastError();
if (err == ERROR_IO_PENDING) {
if (WaitForSingleObject(ovlp.hEvent, INFINITE) == WAIT_OBJECT_0)
GetOverlappedResult(hDevice, &ovlp, &acnt, FALSE);
} else {
error("devrxavail(): error=%d", err);
}
}
CloseHandle(ovlp.hEvent);
}
// do the read if something there, at most size of buffer
ncnt = stat.cbInQue > sizeof(rbuf) ? sizeof(rbuf) : stat.cbInQue;
if (!ReadFile(hDevice, rbuf, ncnt, &acnt, NULL))
error("devrxavail(): error=%d", GetLastError());
// check for break
if (sts & CE_BREAK) rxBreakSeen = 1;
// done
rcnt = acnt;
#else // !WINCOMM
@ -296,7 +249,7 @@ int32_t devrxavail (void)
//
// write characters direct to device
// write characters direct to device from transmit buffer
//
int32_t devtxwrite (uint8_t *buf,
int32_t cnt)
@ -304,28 +257,16 @@ int32_t devtxwrite (uint8_t *buf,
// write characters if asked, return number written
if (cnt > 0) {
#ifdef WINCOMM
OVERLAPPED ovlp = { 0 };
COMSTAT stat;
DWORD acnt = 0;
DWORD sts = 0;
// clear state
if (!ClearCommError(hDevice, &sts, &stat))
error("devtxwrite(): ClearCommError() failed");
if (debug && (sts || stat.cbOutQue))
info("devtxwrite(): status=0x%04X remain=%d", sts, stat.cbOutQue);
// do the write
ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!WriteFile(hDevice, buf, cnt, &acnt, &ovlp)) {
DWORD err = GetLastError();
if (err == ERROR_IO_PENDING) {
if (WaitForSingleObject(ovlp.hEvent, INFINITE) == WAIT_OBJECT_0)
GetOverlappedResult(hDevice, &ovlp, &acnt, FALSE);
} else {
error("devtxwrite(): error=%d", err);
}
}
if (!WriteFile(hDevice, buf, cnt, &acnt, NULL))
error("devtxwrite(): error=%d", GetLastError());
// done
CloseHandle(ovlp.hEvent);
return acnt;
#else // !WINCOMM
return write(device, buf, cnt);
@ -370,14 +311,71 @@ void devtxflush (void)
//
// return char from rbuf, wait until some arrive
//
uint8_t devrxget (void)
uint8_t devrxget (uint8_t *flg)
{
// get more characters if none available
while (devrxavail() <= 0) /*spin*/;
uint8_t c;
// count, return next character
#ifdef USE_PARMRK
// get more bytes if none available
while (rcnt <= 0) { (void)devrxavail(); }
// at least one available
rcnt--;
return *rptr++;
// check if escaped or normal
if ((c = *rptr++) == 0377) {
// escape byte seen
while (rcnt <= 0) { (void)devrxavail(); }
// at least one available
rcnt--;
// check if escape or not
if ((c = *rptr++) == 0377) {
// 377,377 seen; return 377
*flg = DEV_NORMAL;
return c;
} else {
// non-escape byte seen, so get one more byte
while (rcnt <= 0) { (void)devrxavail(); }
// at least one available
rcnt--;
// check if NULL
if ((c = *rptr++) == 0000) {
// 377,000,000 seen; signals a BREAK, return 000 byte
*flg = DEV_BREAK;
return c;
} else {
// 377,000,NNN seen; signals byte NNN parity/framing error
*flg = DEV_ERROR;
return c;
}
}
} else {
// return normal data byte
*flg = DEV_NORMAL;
return c;
}
#else // !USE_PARMRK
// get more characters if none available
while (rcnt <= 0) { (void)devrxavail(); }
// at least one available
rcnt--;
// get data byte
c = *rptr++;
#ifdef WINCOMM
// check if this byte should be flagged as a BREAK
// for lack of a better algorithm, we flag the first
// ZERO byte after the rxBreakSeen flag is set as BREAK
if (c == 000 && rxBreakSeen) {
*flg = DEV_BREAK;
rxBreakSeen = 0;
} else {
*flg = DEV_NORMAL;
}
#else // !WINCOMM
// return normal data byte
*flg = DEV_NORMAL;
#endif // !WINCOMM
// return data byte
return c;
#endif // !USE_PARMRK
}
@ -405,49 +403,89 @@ void devtxput (uint8_t c)
static int32_t devbaud (int32_t rate)
{
#ifdef WINCOMM
static int32_t baudlist[] = { 3000000, 3000000,
2500000, 2500000,
2000000, 2000000,
1500000, 1500000,
1152000, 1152000,
1000000, 1000000,
921600, 921600,
576000, 576000,
500000, 500000,
460800, 460800,
256000, 256000,
230400, 230400,
115200, 115200,
57600, 57600,
38400, 38400,
19200, 19200,
9600, 9600,
4800, 4800,
2400, 2400,
1200, 1200,
-1, -1 };
static int32_t baudlist[] = {
3000000, 3000000,
2500000, 2500000,
2000000, 2000000,
1500000, 1500000,
1152000, 1152000,
1000000, 1000000,
921600, 921600,
576000, 576000,
500000, 500000,
460800, 460800,
230400, 230400,
115200, 115200,
57600, 57600,
38400, 38400,
19200, 19200,
9600, 9600,
4800, 4800,
2400, 2400,
1200, 1200,
-1, -1
};
#else // !WINCOMM
static int32_t baudlist[] = { 3000000, B3000000,
2500000, B2500000,
2000000, B2000000,
1500000, B1500000,
1152000, B1152000,
1000000, B1000000,
921600, B921600,
576000, B576000,
500000, B500000,
460800, B460800,
256000, B256000,
230400, B230400,
115200, B115200,
57600, B57600,
38400, B38400,
19200, B19200,
9600, B9600,
4800, B4800,
2400, B2400,
1200, B1200,
-1, -1 };
static int32_t baudlist[] = {
#ifdef B3000000
3000000, B3000000,
#endif // B3000000
#ifdef B2500000
2500000, B2500000,
#endif // B2500000
#ifdef B2000000
2000000, B2000000,
#endif // B2000000
#ifdef B1500000
1500000, B1500000,
#endif // B1500000
#ifdef B1152000
1152000, B1152000,
#endif // B1152000
#ifdef B1000000
1000000, B1000000,
#endif // B1000000
#ifdef B921600
921600, B921600,
#endif // B921600
#ifdef B576000
576000, B576000,
#endif // B576000
#ifdef B500000
500000, B500000,
#endif // B500000
#ifdef B460800
460800, B460800,
#endif // B460800
#ifdef B230400
230400, B230400,
#endif // B230400
#ifdef B115200
115200, B115200,
#endif // B115200
#ifdef B57600
57600, B57600,
#endif // B57600
#ifdef B38400
38400, B38400,
#endif // B38400
#ifdef B19200
19200, B19200,
#endif // B19200
#ifdef B9600
9600, B9600,
#endif // B9600
#ifdef B4800
4800, B4800,
#endif // B4800
#ifdef B2400
2400, B2400,
#endif // B2400
#ifdef B1200
1200, B1200,
#endif // B1200
-1, -1
};
#endif // !WINCOMM
int32_t *p = baudlist;
int32_t r;
@ -465,7 +503,8 @@ static int32_t devbaud (int32_t rate)
// open/initialize serial port
//
void devinit (char *port,
int32_t speed)
int32_t speed,
int32_t stop)
{
#ifdef WINCOMM
@ -480,9 +519,17 @@ void devinit (char *port,
int32_t uid = getuid();
setreuid(euid, -1);
if (sscanf(port, "%u", &n) == 1) sprintf(name, "\\\\.\\COM%d", n); else strcpy(name, port);
hDevice = CreateFile(name, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, NULL);
// open port in non-overlapped I/O mode
hDevice = CreateFile(name,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hDevice == INVALID_HANDLE_VALUE) fatal("no serial line [%s]", name);
// we own the port
setreuid(uid, euid);
// get current line params, error if not a serial port
@ -515,7 +562,7 @@ void devinit (char *port,
dcb.fAbortOnError = FALSE;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = TWOSTOPBITS;
dcb.StopBits = (stop == 2) ? TWOSTOPBITS : ONESTOPBIT;
// timing/read param
cto.ReadIntervalTimeout = MAXDWORD;
@ -539,10 +586,10 @@ void devinit (char *port,
// open serial port
int32_t euid = geteuid();
int32_t uid = getuid();
setreuid(euid, -1);
if (setreuid(euid, -1)) fatal("setreuid(euid,-1) failed");
if (sscanf(port, "%u", &n) == 1) sprintf(name, "/dev/ttyS%u", n-1); else strcpy(name, port);
if ((device = open(name, O_RDWR|O_NDELAY|O_NOCTTY)) < 0) fatal("no serial line [%s]", name);
setreuid(uid, euid);
if (setreuid(uid, euid)) fatal("setreuid(uid,euid) failed");
// get current line params, error if not a serial port
if (tcgetattr(device, &lineSave)) fatal("not a serial device [%s]", name);
@ -550,17 +597,15 @@ void devinit (char *port,
// copy current parameters
line = lineSave;
// set baud rate
if (devbaud(speed) == -1)
error("illegal serial speed %d., ignoring", speed);
else
line.c_ospeed = line.c_ispeed = devbaud(speed);
// input param
line.c_iflag &= ~( IGNBRK | BRKINT | IMAXBEL | INPCK | ISTRIP |
INLCR | IGNCR | ICRNL | IXON | IXOFF |
IUCLC | IXANY | PARMRK | IGNPAR );
#ifdef USE_PARMRK
line.c_iflag |= ( PARMRK | INPCK );
#else // !USE_PARMRK
line.c_iflag |= ( 0 );
#endif // !USE_PARMRK
// output param
line.c_oflag &= ~( OPOST | OLCUC | OCRNL | ONLCR | ONOCR |
@ -571,7 +616,10 @@ void devinit (char *port,
// control param
line.c_cflag &= ~( CBAUD | CSIZE | CSTOPB | PARENB | PARODD |
HUPCL | CRTSCTS | CLOCAL | CREAD );
line.c_cflag |= ( CLOCAL | CREAD | CS8 | CSTOPB ); // 8b+2stop
line.c_cflag |= ( CLOCAL | CREAD | CS8 );
// set two stop bits if requested, else default to one
if (stop == 2) line.c_cflag |= CSTOPB;
// local param
line.c_lflag &= ~( ISIG | ICANON | ECHO | ECHOE | ECHOK |
@ -583,8 +631,18 @@ void devinit (char *port,
line.c_cc[VMIN] = 1; // return a min of 1 chars
line.c_cc[VTIME] = 0; // no timer
// ok, set new param
// flush all existing input data
tcflush(device, TCIFLUSH);
// set baud rate, if it is legal
if (devbaud(speed) == -1) {
error("illegal serial speed %d., ignoring", speed);
} else {
cfsetispeed(&line, devbaud(speed));
cfsetospeed(&line, devbaud(speed));
}
// set new device parameters
tcsetattr(device, TCSANOW, &line);
// and non-blocking also

View File

@ -2,7 +2,7 @@
// tu58 - Emulate a TU58 over a serial line
//
// Original (C) 1984 Dan Ts'o <Rockefeller Univ. Dept. of Neurobiology>
// Update (C) 2005-2016 Donald N North <ak6dn_at_mindspring_dot_com>
// Update (C) 2005-2017 Donald N North <ak6dn_at_mindspring_dot_com>
//
// All rights reserved.
//
@ -48,22 +48,21 @@
#include "common.h"
#include <pthread.h>
#include <setjmp.h>
#include "tu58.h"
#ifdef MACOSX
// clock_gettime() is not available under OSX
// clock_gettime() is not available under MACOSX
#define CLOCK_REALTIME 1
#include <mach/mach_time.h>
void clock_gettime(int dummy, timespec_t *t) {
void clock_gettime (int dummy, struct timespec *t) {
uint64_t mt;
mt = mach_absolute_time();
t->tv_sec = mt / 1000000000;
t->tv_nsec = mt % 1000000000;
}
#endif
#endif // MACOSX
// delays for modeling device access
@ -90,7 +89,7 @@ uint8_t mrsp = 0; // set nonzero to indicate MRSP mode is active
static uint8_t doinit = 0; // set nonzero to indicate should send INITs continuously
static uint8_t runonce = 0; // set nonzero to indicate emulator has been run
static pthread_t th_run; // emulator thread id
static pthread_t th_monitor; // monitor thread id
static jmp_buf rx_break_env; // longjmp state for when a BREAK is detected on rx
@ -138,6 +137,26 @@ static void reinit (void)
//
// read a byte from the host, process BREAK if seen
//
static uint8_t rxget (void)
{
uint8_t state;
uint8_t c;
// get a byte and state flag
c = devrxget(&state);
// seen a BREAK on the rx line, so abort this packet
if (state == DEV_BREAK) longjmp(rx_break_env, c);
// return the byte
return c;
}
//
// read of boot is not packetized, is just raw data
//
@ -148,7 +167,7 @@ static void bootio (void)
uint8_t buffer[TU_BOOT_LEN];
// check unit number for validity
unit = devrxget();
unit = rxget();
if (fileunit(unit)) {
error("bootio bad unit %d", unit);
return;
@ -238,7 +257,7 @@ static void wait4cont (uint8_t code)
// wait for a CONT to arrive, but only so long
do {
c = devrxget();
c = rxget();
if (debug) info("wait4cont(): char=0x%02X", c);
} while (c != TUF_CONT && --maxchar >= 0);
@ -291,7 +310,7 @@ static int32_t getpacket (tu_packet *pkt)
uint16_t rcvchk, expchk;
// get remaining packet bytes, incl two checksum bytes
while (--count >= 0) *ptr++ = devrxget();
while (--count >= 0) *ptr++ = rxget();
// get checksum bytes
rcvchk = (ptr[-1]<<8) | (ptr[-2]<<0);
@ -483,7 +502,7 @@ static void tuwrite (tu_cmdpkt *pk)
// loop until we see data flag
do {
last = dk.flag;
dk.flag = devrxget();
dk.flag = rxget();
if (debug) info("flag=0x%02X last=0x%02X", dk.flag, last);
if (last == TUF_INIT && dk.flag == TUF_INIT) {
// two in a row is special
@ -505,7 +524,7 @@ static void tuwrite (tu_cmdpkt *pk)
} while (dk.flag != TUF_DATA);
// byte following data flag is packet data length
dk.length = devrxget();
dk.length = rxget();
// get remainder of the data packet
if (getpacket((tu_packet *)&dk)) {
@ -577,7 +596,7 @@ static void command (int8_t flag)
time_end.tv_nsec = 0;
pk.flag = flag;
pk.length = devrxget();
pk.length = rxget();
// check control packet length ... if too long flush it
if (pk.length > sizeof(tu_cmdpkt)) {
@ -729,11 +748,18 @@ static void* run (void* none)
doinit = !nosync; // start sending init flags?
// say hello
info("emulator %sstarted", runonce++ ? "re" : "");
info("TU58 emulator %sstarted", runonce++ ? "re" : "");
// loop forever ... almost
for (;;) {
// setup for when a BREAK is detected
if (setjmp(rx_break_env)) {
// return here when we get a BREAK on the rx input
if (debug) info("<BREAK> seen");
// fall thru to main loop
}
// loop while no characters are available
while (devrxavail() == 0) {
// delays and printout only if not VAX
@ -752,7 +778,7 @@ static void* run (void* none)
// process received characters
last = flag;
flag = devrxget();
flag = rxget();
if (debug) info("flag=0x%02X last=0x%02X", flag, last);
switch (flag) {
@ -818,48 +844,6 @@ static void* run (void* none)
//
// monitor for break/error on line, restart emulator if seen
//
static void* monitor (void* none)
{
int32_t sts;
for (;;) {
// check for any error
switch (sts = devrxerror()) {
case DEV_ERROR: // error
case DEV_BREAK: // break
// kill and restart the emulator
if (verbose) info("BREAK detected");
#ifdef THIS_DOES_NOT_YET_WORK_RELIABLY
if (pthread_cancel(th_run))
error("unable to cancel emulation thread");
if (pthread_join(th_run, NULL))
error("unable to join on emulation thread");
if (pthread_create(&th_run, NULL, run, NULL))
error("unable to restart emulation thread");
#endif // THIS_DOES_NOT_YET_WORK_RELIABLY
break;
case DEV_OK: // OK
break;
case DEV_NYI: // not yet implemented
return (void*)1;
default: // something else...
error("monitor(): unknown flag %d", sts);
break;
}
// bit of a delay, loop again
delay_ms(5);
}
return (void*)0;
}
//
// start tu58 drive emulation
//
@ -870,17 +854,13 @@ void tu58drive (void)
fatal("illegal BLOCKSIZE (%d) / TU_DATA_LEN (%d) ratio", BLOCKSIZE, TU_DATA_LEN);
// say hello
info("TU58 emulation start");
info("TU58 start");
info("R restart, S toggle send init, V toggle verbose, D toggle debug, Q quit");
// run the emulator
if (pthread_create(&th_run, NULL, run, NULL))
error("unable to create emulation thread");
// run the monitor
if (pthread_create(&th_monitor, NULL, monitor, NULL))
error("unable to create monitor thread");
// loop on user input
for (;;) {
uint8_t c;
@ -912,8 +892,6 @@ void tu58drive (void)
error("unable to restart emulation thread");
} else if (c == 'Q') {
// kill the emulator and exit
if (pthread_cancel(th_monitor))
error("unable to cancel monitor thread");
if (pthread_cancel(th_run))
error("unable to cancel emulation thread");
break;
@ -930,7 +908,7 @@ void tu58drive (void)
error("unable to join on emulation thread");
// all done
info("TU58 emulation end");
info("TU58 end");
return;
}