1
0
mirror of synced 2026-01-11 23:52:42 +00:00

tu58 drive emulator

v1.4j
This commit is contained in:
Don North 2015-06-08 10:23:59 -06:00
parent cca01f176b
commit 3c387d5edd
9 changed files with 2391 additions and 0 deletions

106
common.h Normal file
View File

@ -0,0 +1,106 @@
//
// tu58 - Emulate a TU58 over a serial line
//
// Original (C) 1984 Dan Ts'o <Rockefeller Univ. Dept. of Neurobiology>
// Update (C) 2005-2012 Don North <ak6dn_at_mindspring_dot_com>
//
// This is the TU58 emulation program written at Rockefeller Univ., Dept. of
// Neurobiology. We copyright (C) it and permit its use provided it is not
// sold to others. Originally written by Dan Ts'o circa 1984 or so.
//
//
// TU58 Emulator Common Definitions
//
// Common includes
#include <stdint.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#ifndef O_BINARY
#define O_BINARY 0 // for linux compatibility
#endif
#ifndef timespec_t
typedef struct timespec timespec_t;
#endif
// Constants
#define NTU58 8 // number of devices to emulate (0..N-1)
#define TAPESIZE 512 // number of blocks per tape
#define BLOCKSIZE 512 // number of bytes per block
#define FILEREAD 1 // file can be read
#define FILEWRITE 2 // file can be written
#define FILECREATE 3 // file should be created
#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
// Prototypes
// main.c
void fatal (char *, ...);
void error (char *, ...);
void info (char *, ...);
// serial.c
void devtxbreak (void);
void devtxstop (void);
void devtxstart (void);
void devtxinit (void);
void devtxflush (void);
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);
void devrestore (void);
void coninit (void);
void conrestore (void);
int32_t conget (void);
// file.c
void fileinit (void);
int32_t fileopen (char *, int32_t);
int32_t fileunit (int32_t);
int32_t fileseek (int32_t, int32_t, int32_t, int32_t);
int32_t fileread (int32_t, uint8_t *, int32_t);
int32_t filewrite (int32_t, uint8_t *, int32_t);
void fileclose (void);
// tu58drive.c
void tu58drive (void);
// Globals
extern uint8_t debug;
extern uint8_t verbose;
extern uint8_t nosync;
extern uint8_t timing;
extern uint8_t mrspen;
// the end

367
file.c Normal file
View File

@ -0,0 +1,367 @@
//
// tu58 - Emulate a TU58 over a serial line
//
// Original (C) 1984 Dan Ts'o <Rockefeller Univ. Dept. of Neurobiology>
// Update (C) 2005-2012 Don North <ak6dn_at_mindspring_dot_com>
//
// This is the TU58 emulation program written at Rockefeller Univ., Dept. of
// Neurobiology. We copyright (C) it and permit its use provided it is not
// sold to others. Originally written by Dan Ts'o circa 1984 or so.
//
//
// TU58 File access routines
//
#include "common.h"
// file data structure
struct {
int32_t fd; // file descriptor
char *name; // file name
uint8_t rflag : 1; // read allowed
uint8_t wflag : 1; // write allowed
uint8_t cflag : 1; // create allowed
uint8_t iflag : 1; // init RT-11 structure
uint8_t xflag : 1; // init XXDP structure
} file [NTU58];
int32_t fpt; // number of active file descriptors
//
// init file structures for all units
//
void fileinit (void)
{
int32_t unit;
for (unit = 0; unit < NTU58; unit++) {
file[unit].fd = -1;
file[unit].name = NULL;
file[unit].rflag = 0;
file[unit].wflag = 0;
file[unit].cflag = 0;
file[unit].iflag = 0;
file[unit].xflag = 0;
}
fpt = 0;
return;
}
//
// close file structures for all units
//
void fileclose (void)
{
int32_t unit;
for (unit = 0; unit < NTU58; unit++) {
if (file[unit].fd != -1) {
close(file[unit].fd);
file[unit].fd = -1;
}
}
return;
}
//
// init RT-11 file directory structures (based on RT-11 v5.4)
//
static int32_t rt11_init (int32_t fd)
{
int32_t i;
static int16_t boot[] = { // offset 0000000
0000240, 0000005, 0000404, 0000000, 0000000, 0041420, 0116020, 0000400,
0004067, 0000044, 0000015, 0000000, 0005000, 0041077, 0047517, 0026524,
0026525, 0067516, 0061040, 0067557, 0020164, 0067157, 0073040, 0066157,
0066565, 0006545, 0005012, 0000200, 0105737, 0177564, 0100375, 0112037,
0177566, 0100372, 0000777
};
static int16_t bitmap[] = { // offset 0001000
0000000, 0170000, 0007777
};
static int16_t direct1[] = { // offset 0001700
0177777, 0000000, 0000000, 0000000, 0000000, 0000000, 0000000, 0000000,
0000000, 0000001, 0000006, 0107123, 0052122, 0030461, 0020101, 0020040,
0020040, 0020040, 0020040, 0020040, 0020040, 0020040, 0020040, 0020040,
0042504, 0051103, 0030524, 0040461, 0020040, 0020040
};
static int16_t direct2[] = { // offset 0006000
0000001, 0000000, 0000001, 0000000, 0000010, 0001000, 0000325, 0063471,
0023364, 0000770, 0000000, 0002264, 0004000
};
static struct {
int16_t *data;
int16_t length;
int32_t offset;
} table[] = {
{ boot, sizeof(boot), 00000 },
{ bitmap, sizeof(bitmap), 01000 },
{ direct1, sizeof(direct1), 01700 },
{ direct2, sizeof(direct2), 06000 },
{ NULL, 0, 0 }
};
// now write data from the table
for (i = 0; table[i].length; i++) {
lseek(fd, table[i].offset, SEEK_SET);
if (write(fd, table[i].data, table[i].length) != table[i].length) return -1;
}
return 0;
}
//
// init XXDP file directory structures (based on XXDPv2.5)
//
static int32_t xxdp_init (int32_t fd)
{
int32_t i;
static int16_t mfd1[] = { // MFD1
0000002, // ptr to MFD2
0000001, // interleave factor
0000007, // BITMAP start block number
0000007 // ptr to 1st BITMAP block
};
static int16_t mfd2[] = { // MFD2
0000000, // no more MFDs
0000401, // uic [1,1]
0000003, // ptr to 1st UFD block
0000011 // 9. words per UFD entry
};
static int16_t ufd1[] = { // UFD#1 (empty directory)
0000004 // ptr to UFD#2
};
static int16_t ufd2[] = { // UFD#2 (empty directory)
0000005 // ptr to UFD#3
};
static int16_t ufd3[] = { // UFD#3 (empty directory)
0000006 // ptr to UFD#4
};
static int16_t ufd4[] = { // UFD#4 (empty directory)
0000000 // no more UFDs
};
static int16_t map1[] = { // BITMAP#1
0000000, // no more BITMAPs
0000001, // map number
0000074, // 60. words per BITMAP
0000007, // ptr to BITMAP#1
0177777, // blocks 15..00 allocated
0177777, // blocks 31..16 allocated
0000377 // blocks 39..32 allocated
};
static struct {
int16_t *data;
int16_t length;
int32_t offset;
} table[] = {
{ mfd1, sizeof(mfd1), 01000 },
{ mfd2, sizeof(mfd2), 02000 },
{ ufd1, sizeof(ufd1), 03000 },
{ ufd2, sizeof(ufd2), 04000 },
{ ufd3, sizeof(ufd3), 05000 },
{ ufd4, sizeof(ufd4), 06000 },
{ map1, sizeof(map1), 07000 },
{ NULL, 0, 0 }
};
// now write data from the table
for (i = 0; table[i].length; i++) {
lseek(fd, table[i].offset, SEEK_SET);
if (write(fd, table[i].data, table[i].length) != table[i].length) return -1;
}
return 0;
}
//
// init a blank tape image (all zero)
//
static int32_t zero_init (int32_t fd)
{
int32_t i;
int8_t buf[BLOCKSIZE];
// zero a block
memset(buf, 0, sizeof(buf));
// zero a whole tape
lseek(fd, 0, SEEK_SET);
for (i = 0; i < TAPESIZE; i++)
if (write(fd, buf, sizeof(buf)) != sizeof(buf)) return -1;
return 0;
}
//
// open a file for a unit
//
int32_t fileopen (char *name,
int32_t mode)
{
int32_t fd;
// check if we can open any more units
if (fpt >= NTU58) { error("no more units available"); return -1; }
// save some data
file[fpt].name = name;
file[fpt].rflag = 1;
if (mode == FILEWRITE) file[fpt].wflag = 1;
if (mode == FILECREATE) file[fpt].wflag = file[fpt].cflag = 1;
if (mode == FILERT11INIT) file[fpt].wflag = file[fpt].cflag = file[fpt].iflag = 1;
if (mode == FILEXXDPINIT) file[fpt].wflag = file[fpt].cflag = file[fpt].xflag = 1;
// open file if it exists
if (file[fpt].wflag)
fd = open(file[fpt].name, O_BINARY|O_RDWR, 0666);
else
fd = open(file[fpt].name, O_BINARY|O_RDONLY);
// create file if it does not exist
if (fd < 0 && file[fpt].cflag) fd = creat(file[fpt].name, 0666);
if (fd < 0) { error("fileopen cannot open or create '%s'", file[fpt].name); return -2; }
// store opened file information
file[fpt].fd = fd;
// zap tape if requested
if (file[fpt].cflag) {
if (!zero_init(fd)) {
info("initialize tape on '%s'", file[fpt].name);
} else {
error("fileopen cannot init tape on '%s'", file[fpt].name);
return -3;
}
}
// initialize RT-11 directory structure ?
if (file[fpt].iflag) {
if (!rt11_init(fd)) {
info("initialize RT-11 directory on '%s'", file[fpt].name);
} else {
error("fileopen cannot init RT-11 filesystem on '%s'", file[fpt].name);
return -4;
}
}
// initialize XXDP directory structure ?
if (file[fpt].xflag) {
if (!xxdp_init(fd)) {
info("initialize XXDP directory on '%s'", file[fpt].name);
} else {
error("fileopen cannot init XXDP filesystem on '%s'", file[fpt].name);
return -5;
}
}
// output some info...
info("unit %d %c%c%c%c file '%s'",
fpt,
file[fpt].rflag ? 'r' : ' ',
file[fpt].wflag ? 'w' : ' ',
file[fpt].cflag ? 'c' : ' ',
file[fpt].iflag ? 'i' : file[fpt].xflag ? 'x' : ' ',
file[fpt].name);
fpt++;
return 0;
}
//
// check file unit OK
//
int32_t fileunit (int32_t unit)
{
if (unit < 0 || unit >= NTU58 || file[unit].fd == -1) {
error("bad unit %d", unit);
return -1;
}
return 0;
}
//
// seek file (tape) position
//
int32_t fileseek (int32_t unit,
int32_t size,
int32_t block,
int32_t offset)
{
if (fileunit(unit)) return -1;
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;
return 0;
}
//
// read bytes from the tape image file
//
int32_t fileread (int32_t unit,
uint8_t *buffer,
int32_t count)
{
if (fileunit(unit)) return -1;
if (!file[unit].rflag) return -2;
return read(file[unit].fd, buffer, count);
}
//
// write bytes to the tape image file
//
int32_t filewrite (int32_t unit,
uint8_t *buffer,
int32_t count)
{
if (fileunit(unit)) return -1;
if (!file[unit].wflag) return -2;
return write(file[unit].fd, buffer, count);
}
// the end

224
main.c Normal file
View File

@ -0,0 +1,224 @@
//
// tu58 - Emulate a TU58 over a serial line
//
// Original (C) 1984 Dan Ts'o <Rockefeller Univ. Dept. of Neurobiology>
// Update (C) 2005-2012 Don North <ak6dn_at_mindspring_dot_com>
//
// This is the TU58 emulation program written at Rockefeller Univ., Dept. of
// Neurobiology. We copyright (C) it and permit its use provided it is not
// sold to others. Originally written by Dan Ts'o circa 1984 or so.
//
// Extensively rewritten to work reliably under the CYGWIN unix subsystem.
// Has been tested up to 38.4Kbaud hosted on a 1GHz Pentium3 Win2K platform.
// Added native windows serial comm support compile option (for break detect).
// Added multithreaded supervisor for command parser and emulator restart.
// Added timing delay option to allow emulator to pass diagnostic ZTUUF0.BIN.
// Supports four (or more, compile option) logical TU58 devices per controller.
//
// v1.0b - 12 Jul 2005 - donorth - Initial rewrite
// v1.1b - 20 Feb 2006 - donorth - Fixed RT11 f/s init; added XXDP f/s init
// v1.2b - 25 Feb 2006 - donorth - Updated -p arg to be number or string
// - Fixed some typos in help string
// v1.3b - 28 Feb 2006 - donorth - Updated windoze serial line config
// v1.4b - 25 May 2006 - donorth - Send INITs on initialization/restart
// v1.4c - 25 Nov 2007 - donorth - Make <INIT><INIT> message be debug, not verbose
// - Fix fileseek() to correctly detect LEOF
// - read/write no longer skip past LEOF
// v1.4d - 10 Feb 2008 - donorth - added XRSP switch for packet-level handshake
// v1.4e - 16 Feb 2008 - donorth - changed to --long switches from -s
// - Slightly updated RT-11 disk init bits
// - Added --nosync switch
// v1.4f - 02 Jan 2012 - donorth - Restructuring for ultimate inclusion
// of a tu58 controller implementation
// (ie, to hook to a real tu58 drive or the emulator)
// v1.4g - 30 Oct 2013 - donorth - Removed XRSP switch
// v1.4h - 03 Nov 2013 - donorth - Added packet timing computation in debug mode
// v1.4i - 14 Nov 2013 - donorth - Disabled devtxflush() in wait4cont() routine
// to fix USB output (PC to device) thruput and
// allow 64B USB packets to be used
// v1.4j - 01 Nov 2014 - donorth - Change 'int' to 'long' where possible.
// Only use char/short/long types, not int.
// Fix source for ubuntu linux 12.04 (time structs)
//
#include "common.h"
#include <getopt.h>
static char copyright[] = "(C) 2005-2014 Don North <ak6dn" "@" "mindspring.com>, " \
"(C) 1984 Dan Ts'o <Rockefeller University>";
static char version[] = "tu58 tape emulator v1.4j";
static char port[32] = "1"; // default port number (COM1, /dev/ttyS0)
static long speed = 9600; // default line speed
uint8_t verbose = 0; // set nonzero to output more info
uint8_t timing = 0; // set nonzero to add timing delays
uint8_t mrspen = 0; // set nonzero to enable MRSP mode
uint8_t nosync = 0; // set nonzero to skip sending INIT at restart
uint8_t debug = 0; // set nonzero for debug output
//
// print an info message and return
//
void info (char *fmt, ...)
{
va_list args;
va_start(args, fmt);
fprintf(stderr, "info: ");
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
va_end(args);
return;
}
//
// print an error message and return
//
void error (char *fmt, ...)
{
va_list args;
va_start(args, fmt);
fprintf(stderr, "ERROR: ");
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
va_end(args);
return;
}
//
// print an error message and die
//
void fatal (char *fmt, ...)
{
va_list args;
va_start(args, fmt);
fprintf(stderr, "FATAL: ");
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
va_end(args);
exit(EXIT_FAILURE);
}
//
// main program
//
int main (int argc,
char **argv)
{
long i;
long n = 0;
long errors = 0;
// switch options
int opt_index = 0;
char opt_short[] = "dvVmnTtp:s:r:w:c:i:z:";
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' },
{ "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 }
};
// init file structures
fileinit();
// process command line options
while ((i = getopt_long(argc, argv, opt_short, opt_long, &opt_index)) != EOF) {
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 'r': fileopen(optarg, FILEREAD); n++; break;
case 'w': fileopen(optarg, FILEWRITE); n++; break;
case 'c': fileopen(optarg, FILECREATE); n++; break;
case 'i': fileopen(optarg, FILERT11INIT); n++; break;
case 'z': fileopen(optarg, FILEXXDPINIT); n++; break;
case 'm': mrspen = 1; break;
case 'n': nosync = 1; break;
case 'T': timing = 2; break;
case 't': timing = 1; break;
case 'd': verbose = 1; debug = 1; break;
case 'v': verbose = 1; break;
case 'V': info("version is %s", version); break;
default: errors++; break;
}
}
// some debug info
if (debug) { info(version); info(copyright); }
// must have opened at least one unit
if (n == 0) {
error("no units were specified");
errors++;
}
// any error seen, die and print out some help
if (errors)
fatal("illegal command line\n" \
" %s\n" \
" Usage: %s [-options] -[rwci] file1 ... -[rwci] file%d\n" \
" Options: -V | --version output version string\n" \
" -v | --verbose enable verbose output to terminal\n" \
" -d | --debug enable debug output to terminal\n" \
" -m | --mrsp enable standard MRSP mode (byte-level handshake)\n" \
" -n | --nosync disable sending INIT at initial startup\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..230400; default 9600]\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" \
" -c | --create FILENAME create new r/w drive, zero tape\n" \
" -i | --initrt11 FILENAME create new r/w drive, initialize RT11 directory\n" \
" -z | --initxxdp FILENAME create new r/w drive, initialize XXDP directory\n",
version, argv[0], NTU58-1);
// give some info
info("serial port %s at %d baud", port, speed);
if (mrspen) info("MRSP mode enabled (NOT fully tested - use with caution)");
// setup serial and console ports
devinit(port, speed);
coninit();
// play TU58
tu58drive();
// restore serial and console ports
conrestore();
devrestore();
// close files we opened
fileclose();
// and done
return EXIT_SUCCESS;
}
// the end

60
makefile Normal file
View File

@ -0,0 +1,60 @@
#
# tu58em emulator makefile
#
ifeq ($(comm),win)
# WINDOWS comms model
PROG = tu58ew
COMM = -DWINCOMM
else
# UNIX comms model
PROG = tu58em
COMM = -UWINCOMM
endif
BIN = ../../../../../tools/exe
CC = gcc
CFLAGS = -I. -O3 -Wall -c $(COMM)
LFLAGS = -lpthread -lrt
$(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
installall :
make --always comm=win install
make clean
make --always comm=unix install
make clean
clean :
-rm -f *.o
-chmod a-x,ug+w,o-w *.c *.h Makefile
-chmod a+rx $(PROG) $(PROG).exe
-chown `whoami` *
purge : clean
-rm -f $(PROG) $(PROG).exe
install : $(PROG)
cp $< $(BIN)
serial.o : serial.c common.h
$(CC) $(CFLAGS) serial.c
main.o : main.c common.h
$(CC) $(CFLAGS) main.c
tu58drive.o : tu58drive.c tu58.h common.h
$(CC) $(CFLAGS) tu58drive.c
file.o : file.c common.h
$(CC) $(CFLAGS) file.c
# the end

623
serial.c Normal file
View File

@ -0,0 +1,623 @@
//
// tu58 - Emulate a TU58 over a serial line
//
// Original (C) 1984 Dan Ts'o <Rockefeller Univ. Dept. of Neurobiology>
// Update (C) 2005-2012 Don North <ak6dn_at_mindspring_dot_com>
//
// This is the TU58 emulation program written at Rockefeller Univ., Dept. of
// Neurobiology. We copyright (C) it and permit its use provided it is not
// sold to others. Originally written by Dan Ts'o circa 1984 or so.
//
//
// TU58 serial support routines
//
#include "common.h"
#ifdef WINCOMM
#include <windef.h>
#include <winbase.h>
#endif // WINCOMM
#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 *wptr;
static int32_t wcnt;
// serial input buffer
static uint8_t rbuf[BUFSIZE];
static uint8_t *rptr;
static int32_t rcnt;
#ifdef WINCOMM
// serial device descriptor, default to nada
static HANDLE hDevice = INVALID_HANDLE_VALUE;
// async line parameters
static DCB dcbSave;
static COMMTIMEOUTS ctoSave;
#else // !WINCOMM
// serial device descriptor, default to nada
static int32_t device = -1;
// async line parameters
static struct termios lineSave;
#endif // !WINCOMM
// console parameters
static struct termios consSave;
#ifdef WINCOMM
//
// delay routine
//
static void delay_ms (int32_t ms)
{
struct timespec rqtp;
int32_t sts;
// check if any delay required
if (ms <= 0) return;
// compute integer seconds and fraction (in nanoseconds)
rqtp.tv_sec = ms / 1000L;
rqtp.tv_nsec = (ms % 1000L) * 1000000L;
// if nanosleep() fails then just plain sleep()
if ((sts = nanosleep(&rqtp, NULL)) == -1) sleep(rqtp.tv_sec);
return;
}
#endif // WINCOMM
//
// stop transmission on output
//
void devtxstop (void)
{
#ifdef WINCOMM
if (!EscapeCommFunction(hDevice, SETXOFF))
error("devtxstop(): error=%d", GetLastError());
#else // !WINCOMM
tcflow(device, TCOOFF);
#endif // !WINCOMM
return;
}
//
// (re)start transmission on output
//
void devtxstart (void)
{
#ifdef WINCOMM
if (!EscapeCommFunction(hDevice, SETXON))
error("devtxstart(): error=%d", GetLastError());
#else // !WINCOMM
tcflow(device, TCOON);
#endif // !WINCOMM
return;
}
//
// set/clear break condition on output
//
void devtxbreak (void)
{
#ifdef WINCOMM
if (!SetCommBreak(hDevice))
error("devtxbreak(set): error=%d", GetLastError());
delay_ms(250);
if (!ClearCommBreak(hDevice))
error("devtxbreak(clear): error=%d", GetLastError());
#else // !WINCOMM
tcsendbreak(device, 0);
#endif // !WINCOMM
return;
}
//
// initialize tx serial buffers
//
void devtxinit (void)
{
// flush all output
#ifdef WINCOMM
if (!PurgeComm(hDevice, PURGE_TXABORT|PURGE_TXCLEAR))
error("devtxinit(): error=%d", GetLastError());
#else // !WINCOMM
tcflush(device, TCOFLUSH);
#endif // !WINCOMM
// reset send buffer
wcnt = 0;
wptr = wbuf;
return;
}
//
// initialize rx serial buffers
//
void devrxinit (void)
{
// flush all input
#ifdef WINCOMM
if (!PurgeComm(hDevice, PURGE_RXABORT|PURGE_RXCLEAR))
error("devrxinit(): error=%d", GetLastError());
#else // !WINCOMM
tcflush(device, TCIFLUSH);
#endif // !WINCOMM
// reset receive buffer
rcnt = 0;
rptr = rbuf;
return;
}
//
// 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
//
int32_t devrxavail (void)
{
// get more characters if none available
if (rcnt <= 0) {
#ifdef WINCOMM
OVERLAPPED ovlp = { 0 };
COMSTAT stat;
DWORD acnt = 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);
}
// done
rcnt = acnt;
#else // !WINCOMM
rcnt = read(device, rbuf, sizeof(rbuf));
#endif // !WINCOMM
rptr = rbuf;
}
if (rcnt < 0) rcnt = 0;
// return characters available
return rcnt;
}
//
// write characters direct to device
//
int32_t devtxwrite (uint8_t *buf,
int32_t cnt)
{
// 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);
}
}
// done
CloseHandle(ovlp.hEvent);
return acnt;
#else // !WINCOMM
return write(device, buf, cnt);
#endif // !WINCOMM
}
// nothing done if we got here
return 0;
}
//
// send any outgoing characters in buffer
//
void devtxflush (void)
{
int32_t acnt;
// write any characters we have
if (wcnt > 0) {
if ((acnt = devtxwrite(wbuf, wcnt)) != wcnt)
error("devtxflush(): write error, expected=%d, actual=%d", wcnt, acnt);
}
// buffer is now empty
wcnt = 0;
wptr = wbuf;
// wait until all characters are transmitted
#ifdef WINCOMM
if (!FlushFileBuffers(hDevice))
error("devtxflush(): FlushFileBuffers() failed, error=%d", GetLastError());
#else // !WINCOMM
tcdrain(device);
#endif // !WINCOMM
return;
}
//
// return char from rbuf, wait until some arrive
//
uint8_t devrxget (void)
{
// get more characters if none available
while (devrxavail() <= 0) /*spin*/;
// count, return next character
rcnt--;
return *rptr++;
}
//
// put char on wbuf
//
void devtxput (uint8_t c)
{
// must flush if hit the end of the buffer
if (wcnt >= sizeof(wbuf)) devtxflush();
// count, add one character to buffer
wcnt++;
*wptr++ = c;
return;
}
//
// return baud rate mask for a given rate
//
static int32_t devbaud (int32_t rate)
{
#ifdef WINCOMM
static int32_t baudlist[] = { 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[] = { 230400, B230400,
115200, B115200,
57600, B57600,
38400, B38400,
19200, B19200,
9600, B9600,
4800, B4800,
2400, B2400,
1200, B1200,
-1, -1 };
#endif // !WINCOMM
int32_t *p = baudlist;
int32_t r;
// search table for a baud rate match, return corresponding entry
while ((r = *p++) != -1) if (r == rate) return *p; else p++;
// not found ...
return -1;
}
//
// open/initialize serial port
//
void devinit (char *port,
int32_t speed)
{
#ifdef WINCOMM
// init win32 serial port mode
DCB dcb;
COMMTIMEOUTS cto;
char name[64];
int32_t n;
// open serial port
int32_t euid = geteuid();
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);
if (hDevice == INVALID_HANDLE_VALUE) fatal("no serial line [%s]", name);
setreuid(uid, euid);
// get current line params, error if not a serial port
if (!GetCommState(hDevice, &dcbSave)) fatal("GetCommState() failed");
if (!GetCommTimeouts(hDevice, &ctoSave)) fatal("GetCommTimeouts() failed");
// copy current parameters
dcb = dcbSave;
cto = ctoSave;
// set baud rate
if (devbaud(speed) == -1)
error("illegal serial speed %d., ignoring", speed);
else
dcb.BaudRate = devbaud(speed);
// update other line parameters
dcb.fBinary = TRUE;
dcb.fParity = FALSE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
dcb.fDsrSensitivity = FALSE;
dcb.fTXContinueOnXoff = TRUE;
dcb.fInX = FALSE;
dcb.fOutX = FALSE;
dcb.fNull = FALSE;
dcb.fErrorChar = FALSE;
dcb.fAbortOnError = FALSE;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
// timing/read param
cto.ReadIntervalTimeout = MAXDWORD;
cto.ReadTotalTimeoutMultiplier = 0;
cto.ReadTotalTimeoutConstant = 0;
cto.WriteTotalTimeoutMultiplier = 0;
cto.WriteTotalTimeoutConstant = 0;
// ok, set new param
if (!SetupComm(hDevice, BUFSIZE, BUFSIZE)) fatal("SetupComm() failed");
if (!SetCommState(hDevice, &dcb)) fatal("SetCommState() failed");
if (!SetCommTimeouts(hDevice, &cto)) fatal("SetCommTimeouts() failed");
#else // !WINCOMM
// init unix serial port mode
struct termios line;
char name[64];
unsigned int n;
// open serial port
int32_t euid = geteuid();
int32_t uid = getuid();
setreuid(euid, -1);
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);
// get current line params, error if not a serial port
if (tcgetattr(device, &lineSave)) fatal("not a serial device [%s]", name);
// 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 );
line.c_iflag |= ( 0 );
// output param
line.c_oflag &= ~( OPOST | OLCUC | OCRNL | ONLCR | ONOCR |
ONLRET | OFILL | CRDLY | NLDLY | BSDLY |
TABDLY | VTDLY | FFDLY | OFDEL );
line.c_oflag |= ( 0 );
// control param
line.c_cflag &= ~( CBAUD | CSIZE | CSTOPB | PARENB | PARODD |
HUPCL | CRTSCTS | CLOCAL | CREAD );
line.c_cflag |= ( CLOCAL | CREAD | CS8 );
// local param
line.c_lflag &= ~( ISIG | ICANON | ECHO | ECHOE | ECHOK |
ECHONL | NOFLSH | TOSTOP | IEXTEN | FLUSHO |
ECHOKE | ECHOCTL );
line.c_lflag |= ( 0 );
// timing/read param
line.c_cc[VMIN] = 1; // return a min of 1 chars
line.c_cc[VTIME] = 0; // no timer
// ok, set new param
tcflush(device, TCIFLUSH);
tcsetattr(device, TCSANOW, &line);
// and non-blocking also
if (fcntl(device, F_SETFL, FNDELAY) == -1)
error("failed to set non-blocking read");
#endif // !WINCOMM
// zap current data, if any
devtxinit();
devrxinit();
return;
}
//
// restore/close serial port
//
void devrestore (void)
{
#ifdef WINCOMM
if (!CloseHandle(hDevice))
error("devrestore(): error=%d", GetLastError());
hDevice = INVALID_HANDLE_VALUE;
#else // !WINCOMM
tcsetattr(device, TCSANOW, &lineSave);
close(device);
device = -1;
#endif // !WINCOMM
return;
}
//
// set console line parameters
//
void coninit (void)
{
struct termios cons;
// get current console parameters
if (tcgetattr(fileno(stdin), &consSave))
fatal("stdin not a serial device");
// copy modes
cons = consSave;
// set new modes
cons.c_lflag &= ~( ICANON | ECHO );
// now set param
tcflush(fileno(stdin), TCIFLUSH);
tcsetattr(fileno(stdin), TCSANOW, &cons);
// set non-blocking reads
if (fcntl(fileno(stdin), F_SETFL, FNDELAY) == -1)
error("stdin failed to set non-blocking read");
return;
}
//
// restore console line parameters
//
void conrestore (void)
{
tcsetattr(fileno(stdin), TCSANOW, &consSave);
return;
}
//
// get console character
//
int32_t conget (void)
{
char buf[1];
int32_t s;
// try to read at most one char (may be none)
s = read(fileno(stdin), buf, sizeof(buf));
// if got a char return it, else return -1
return s == 1 ? *buf : -1;
}
// the end

127
tu58.h Normal file
View File

@ -0,0 +1,127 @@
//
// tu58 - Emulate a TU58 over a serial line
//
// Original (C) 1984 Dan Ts'o <Rockefeller Univ. Dept. of Neurobiology>
// Update (C) 2005-2012 Don North <ak6dn_at_mindspring_dot_com>
//
// This is the TU58 emulation program written at Rockefeller Univ., Dept. of
// Neurobiology. We copyright (C) it and permit its use provided it is not
// sold to others. Originally written by Dan Ts'o circa 1984 or so.
//
// TU58 Radial Serial Protocol
// Packet Flag / Single Byte Commands
#define TUF_NULL 0 // null
#define TUF_DATA 1 // data packet
#define TUF_CTRL 2 // control packet
#define TUF_INIT 4 // initialize
#define TUF_BOOT 8 // boot
#define TUF_CONT 16 // continue
#define TUF_XON 17 // flow control start (XON)
#define TUF_XOFF 19 // flow control stop (XOFF)
// Opcodes
#define TUO_NOP 0 // no operation
#define TUO_INIT 1 // initialize
#define TUO_READ 2 // read block
#define TUO_WRITE 3 // write block
#define TUO_SEEK 5 // seek to block
#define TUO_DIAGNOSE 7 // run diagnostics
#define TUO_GETSTATUS 8 // get status
#define TUO_SETSTATUS 9 // set status
#define TUO_GETCHAR 10 // get characteristics
#define TUO_END 64 // end packet
// Modifiers
#define TUM_RDRS 1 // read with reduced sensitivity
#define TUM_WRRV 1 // write with read verify
#define TUM_B128 128 // special addressing mode
// Switches
#define TUS_MRSP 8 // modified RSP sync mode
#define TUS_MAIN 16 // maintenance mode
// End packet success codes
#define TUE_SUCC 0 // success
#define TUE_SUCR 1 // success with retry
#define TUE_FAIL -1 // failed self test
#define TUE_PARO -2 // partial operation
#define TUE_BADU -8 // bad unit
#define TUE_BADF -9 // no cartridge
#define TUE_WPRO -11 // write protected
#define TUE_DERR -17 // data check error
#define TUE_SKRR -32 // seek error
#define TUE_MTRS -33 // motor stopped
#define TUE_BADO -48 // bad op code
#define TUE_BADB -55 // bad block number
#define TUE_COMM -127 // communications error
// lengths of packets
#define TU_CTRL_LEN 10 // size of cmd packet (opcode..block bytes)
#define TU_DATA_LEN 128 // size of data transfer segment
#define TU_CHAR_LEN 24 // size of getchar data packet
#define TU_BOOT_LEN 512 // size of a boot block
// Packet format, cmd/end vs data
#ifdef __PCH__
typedef struct {
uint8 flag; // packet type
uint8 length; // message length
uint8 opcode; // operation code
uint8 modifier; // modifier
uint8 unit; // drive number
uint8 switches; // switches
uint16 sequence; // sequence number, always zero
uint16 count; // byte count for read or write
uint16 block; // block number for read, write, or seek
uint16 chksum; // checksum, 16b end-around carry
} tu_cmdpkt;
typedef struct {
uint8 flag; // packet type
uint8 length; // message length
uint8 data[TU_DATA_LEN]; // ptr to 1..DATALEN data bytes
uint16 chksum; // checksum, 16b end-around carry
} tu_datpkt;
#else
typedef struct {
uint8_t flag; // packet type
uint8_t length; // message length
uint8_t opcode; // operation code
uint8_t modifier; // modifier
uint8_t unit; // drive number
uint8_t switches; // switches
uint16_t sequence; // sequence number, always zero
uint16_t count; // byte count for read or write
uint16_t block; // block number for read, write, or seek
uint16_t chksum; // checksum, 16b end-around carry
} tu_cmdpkt;
typedef struct {
uint8_t flag; // packet type
uint8_t length; // message length
uint8_t data[TU_DATA_LEN]; // ptr to 1..DATALEN data bytes
uint16_t chksum; // checksum, 16b end-around carry
} tu_datpkt;
#endif
typedef union { // either:
tu_cmdpkt cmd; // a control packet
tu_datpkt dat; // a data packet
} tu_packet;
// the end

884
tu58drive.c Normal file
View File

@ -0,0 +1,884 @@
//
// tu58 - Emulate a TU58 over a serial line
//
// Original (C) 1984 Dan Ts'o <Rockefeller Univ. Dept. of Neurobiology>
// Update (C) 2005-2012 Don North <ak6dn_at_mindspring_dot_com>
//
// This is the TU58 emulation program written at Rockefeller Univ., Dept. of
// Neurobiology. We copyright (C) it and permit its use provided it is not
// sold to others. Originally written by Dan Ts'o circa 1984 or so.
//
//
// TU58 Drive Emulator Machine
//
#include "common.h"
#include <pthread.h>
#include "tu58.h"
// delays for modeling device access
static struct {
uint16_t nop; // ms per NOP, STATUS commands
uint16_t init; // ms per INIT command
uint16_t test; // ms per DIAGNOSE command
uint16_t seek; // ms per SEEK command (s.b. variable)
uint16_t read; // ms per READ 128B packet command
uint16_t write; // ms per WRITE 128B packet command
} tudelay[] = {
// nop init test seek read write
{ 1, 1, 1, 0, 0, 0 }, // timing=0 infinitely fast...
{ 1, 1, 25, 25, 25, 25 }, // timing=1 fast enough to fool diagnostic
{ 1, 1, 25, 200, 100, 100 }, // timing=2 closer to real TU58 behavior
};
// global state
uint8_t mrsp = 0; // set nonzero to indicate MRSP mode is active
// local state
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
//
// delay routine
//
static void delay_ms (int32_t ms)
{
timespec_t rqtp;
int32_t sts;
// check if any delay required
if (ms <= 0) return;
// compute integer seconds and fraction (in nanoseconds)
rqtp.tv_sec = ms / 1000L;
rqtp.tv_nsec = (ms % 1000L) * 1000000L;
// if nanosleep() fails then just plain sleep()
if ((sts = nanosleep(&rqtp, NULL)) == -1) sleep(rqtp.tv_sec);
return;
}
//
// reinitialize TU58 state
//
static void reinit (void)
{
// clear all buffers, wait a bit
devrxinit();
devtxinit();
delay_ms(5);
// init sequence, send immediately
devtxstart();
devtxput(TUF_INIT);
devtxput(TUF_INIT);
devtxflush();
return;
}
//
// read of boot is not packetized, is just raw data
//
static void bootio (void)
{
int32_t unit;
int32_t count;
uint8_t buffer[TU_BOOT_LEN];
// check unit number for validity
unit = devrxget();
if (fileunit(unit)) {
error("bootio bad unit %d", unit);
return;
}
if (verbose) info("%-8s unit=%d blk=0x%04X cnt=0x%04X", "boot", unit, 0, TU_BOOT_LEN);
// seek to block zero, should never be an error :-)
if (fileseek(unit, 0, 0, 0)) {
error("boot seek error unit %d", unit);
return;
}
// read one block of data
if ((count = fileread(unit, buffer, TU_BOOT_LEN)) != TU_BOOT_LEN) {
error("boot file read error unit %d, expected %d, received %d", unit, TU_BOOT_LEN, count);
return;
}
// write one block of data to serial line
if ((count = devtxwrite(buffer, TU_BOOT_LEN)) != TU_BOOT_LEN) {
error("boot serial write error unit %d, expected %d, received %d", unit, TU_BOOT_LEN, count);
return;
}
return;
}
//
// debug dump a packet to stderr
//
static void dumppacket (tu_packet *pkt, char *name)
{
int32_t count = 0;
uint8_t *ptr = (uint8_t *)pkt;
fprintf(stderr, "info: %s()\n", name);
while (count++ < pkt->cmd.length+2) {
if (count == 3 || ((count-4)%32 == 31)) fprintf(stderr, "\n");
fprintf(stderr, " %02X", *ptr++);
}
fprintf(stderr, "\n %02X %02X\n", ptr[0], ptr[1]);
return;
}
//
// compute checksum of a TU58 packet
//
static uint16_t checksum (tu_packet *pkt)
{
int32_t count = pkt->cmd.length + 2; // +2 for flag/length bytes
uint8_t *ptr = (uint8_t *)pkt; // start at flag byte
uint32_t chksum = 0; // initial checksum value
while (--count >= 0) {
chksum += *ptr++; // at least one byte
if (--count >= 0) chksum += (*ptr++ << 8); // at least two bytes
chksum = (chksum + (chksum >> 16)) & 0xFFFF; // 16b end around carry
}
return chksum;
}
//
// wait for a CONT to arrive
//
static void wait4cont (uint8_t code)
{
uint8_t c;
int32_t maxchar = TU_CTRL_LEN+TU_DATA_LEN+8;
// send any existing data out ... makes USB serial emulation be real slow if enabled!
if (0) devtxflush();
// don't do any waiting if flag not set
if (!code) return;
// wait for a CONT to arrive, but only so long
do {
c = devrxget();
if (debug) info("wait4cont(): char=0x%02X", c);
} while (c != TUF_CONT && --maxchar >= 0);
// all done
return;
}
//
// put a packet
//
static void putpacket (tu_packet *pkt)
{
int32_t count = pkt->cmd.length + 2; // +2 for flag/length bytes
uint8_t *ptr = (uint8_t *)pkt; // start at flag byte
uint16_t chksum;
// send all packet bytes
while (--count >= 0) {
devtxput(*ptr++);
wait4cont(mrsp);
}
// compute/send checksum bytes, append to packet
chksum = checksum(pkt);
devtxput(*ptr++ = chksum>>0);
wait4cont(mrsp);
devtxput(*ptr++ = chksum>>8);
wait4cont(mrsp);
// for debug...
if (debug) dumppacket(pkt, "putpacket");
// now actually send the packet (or whatever is left to send)
devtxflush();
return;
}
//
// get a packet
//
static int32_t getpacket (tu_packet *pkt)
{
int32_t count = pkt->cmd.length + 2; // +2 for checksum bytes
uint8_t *ptr = (uint8_t *)pkt + 2; // skip over flag/length bytes
uint16_t rcvchk, expchk;
// get remaining packet bytes, incl two checksum bytes
while (--count >= 0) *ptr++ = devrxget();
// get checksum bytes
rcvchk = (ptr[-1]<<8) | (ptr[-2]<<0);
// compute expected checksum
expchk = checksum(pkt);
// for debug...
if (debug) dumppacket(pkt, "getpacket");
// message on error
if (expchk != rcvchk)
error("getpacket checksum error: exp=0x%04X rcv=0x%04X", expchk, rcvchk);
// return checksum match indication
return (expchk != rcvchk);
}
//
// tu58 sends end packet to host
//
static void endpacket (uint8_t unit,
uint8_t code,
uint16_t count,
uint16_t status)
{
static tu_cmdpkt ek = { TUF_CTRL, TU_CTRL_LEN, TUO_END, 0, 0, 0, 0, 0, 0, -1 };
ek.unit = unit;
ek.modifier = code; // success/fail code
ek.count = count;
ek.block = status; // summary status
putpacket((tu_packet *)&ek);
devtxflush(); // finish packet transmit
return;
}
//
// return requested block size of a tu58 access
//
static inline int32_t blocksize (uint8_t modifier)
{
return (modifier & TUM_B128) ? BLOCKSIZE/4 : BLOCKSIZE;
}
//
// host seek of tu58
//
static void tuseek (tu_cmdpkt *pk)
{
// check unit number for validity
if (fileunit(pk->unit)) {
error("tuseek bad unit %d", pk->unit);
endpacket(pk->unit, TUE_BADU, 0, 0);
return;
}
// seek to desired block
if (fileseek(pk->unit, blocksize(pk->modifier), pk->block, 0)) {
error("tuseek unit %d bad block 0x%04X", pk->unit, pk->block);
endpacket(pk->unit, TUE_BADB, 0, 0);
return;
}
// fake a seek time
delay_ms(tudelay[timing].seek);
// success if we get here
endpacket(pk->unit, TUE_SUCC, 0, 0);
return;
}
//
// host read from tu58
//
static void turead (tu_cmdpkt *pk)
{
int32_t count;
tu_datpkt dk;
// check unit number for validity
if (fileunit(pk->unit)) {
error("turead bad unit %d", pk->unit);
endpacket(pk->unit, TUE_BADU, 0, 0);
return;
}
// seek to desired ending block offset
if (fileseek(pk->unit, blocksize(pk->modifier), pk->block, pk->count-1)) {
error("turead unit %d bad block 0x%04X", pk->unit, pk->block);
endpacket(pk->unit, TUE_BADB, 0, 0);
return;
}
// seek to desired starting block offset
if (fileseek(pk->unit, blocksize(pk->modifier), pk->block, 0)) {
error("turead unit %d bad block 0x%04X", pk->unit, pk->block);
endpacket(pk->unit, TUE_BADB, 0, 0);
return;
}
// fake a seek time
delay_ms(tudelay[timing].seek);
// send data in packets until we run out
for (count = pk->count; count > 0; count -= dk.length) {
// max bytes to send at once is TU_DATA_LEN
dk.flag = TUF_DATA;
dk.length = count < TU_DATA_LEN ? count : TU_DATA_LEN;
if (fileread(pk->unit, dk.data, dk.length) == dk.length) {
// successful file read, send packet
putpacket((tu_packet *)&dk);
// fake a read time
delay_ms(tudelay[timing].read);
} else {
// whoops, something bad happened
error("turead unit %d data error block 0x%04X count 0x%04X",
pk->unit, pk->block, pk->count);
endpacket(pk->unit, TUE_PARO, pk->count-count, 0);
return;
}
}
// success if we get here
endpacket(pk->unit, TUE_SUCC, pk->count, 0);
return;
}
//
// host write to tu58
//
static void tuwrite (tu_cmdpkt *pk)
{
int32_t count;
int32_t status;
tu_datpkt dk;
// check unit number for validity
if (fileunit(pk->unit)) {
error("tuwrite bad unit %d", pk->unit);
endpacket(pk->unit, TUE_BADU, 0, 0);
return;
}
// seek to desired ending block offset
if (fileseek(pk->unit, blocksize(pk->modifier), pk->block, pk->count-1)) {
error("tuwrite unit %d bad block 0x%04X", pk->unit, pk->block);
endpacket(pk->unit, TUE_BADB, 0, 0);
return;
}
// seek to desired starting block offset
if (fileseek(pk->unit, blocksize(pk->modifier), pk->block, 0)) {
error("tuwrite unit %d bad block 0x%04X", pk->unit, pk->block);
endpacket(pk->unit, TUE_BADB, 0, 0);
return;
}
// fake a seek time
delay_ms(tudelay[timing].seek);
// keep looping if more data is expected
for (count = pk->count; count > 0; count -= dk.length) {
// send continue flag; we are ready for more data
devtxput(TUF_CONT);
devtxflush();
if (debug) info("sending <CONT>");
uint8_t last;
dk.flag = -1;
// loop until we see data flag
do {
last = dk.flag;
dk.flag = devrxget();
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
devtxput(TUF_CONT); // send 'continue'
devtxflush(); // send immediate
if (debug) info("<INIT><INIT> seen, sending <CONT>, abort write");
return; // abort command
} else if (dk.flag == TUF_CTRL) {
error("protocol error, unexpected CTRL flag during write");
endpacket(pk->unit, TUE_DERR, 0, 0);
return;
} else if (dk.flag == TUF_XOFF) {
if (debug) info("<XOFF> seen, stopping output");
devtxstop();
} else if (dk.flag == TUF_CONT) {
if (debug) info("<CONT> seen, starting output");
devtxstart();
}
} while (dk.flag != TUF_DATA);
// byte following data flag is packet data length
dk.length = devrxget();
// get remainder of the data packet
if (getpacket((tu_packet *)&dk)) {
// whoops, checksum error, fail
error("data checksum error");
endpacket(pk->unit, TUE_DERR, 0, 0);
return;
}
// write data packet to file
if ((status = filewrite(pk->unit, dk.data, dk.length)) != dk.length) {
if (status == -2) {
// whoops, unit is write protected
error("tuwrite unit %d is write protected block 0x%04X count 0x%04X",
pk->unit, pk->block, pk->count);
endpacket(pk->unit, TUE_WPRO, pk->count-count, 0);
} else {
// whoops, some other data write error (like past EOF)
error("tuwrite unit %d data write error block 0x%04X count 0x%04X",
pk->unit, pk->block, pk->count);
endpacket(pk->unit, TUE_PARO, pk->count-count, 0);
}
return;
}
// fake a write time
delay_ms(tudelay[timing].write);
}
// must fill out last block with zeros
if ((count = pk->count % blocksize(pk->modifier)) > 0) {
uint8_t buffer[BLOCKSIZE];
bzero(buffer, (count = blocksize(pk->modifier)-count));
if (debug) info("tuwrite unit %d filling %d zeroes", pk->unit, count);
if (filewrite(pk->unit, buffer, count) != count) {
// whoops, something bad happened
error("tuwrite unit %d data error block 0x%04X count 0x%04X",
pk->unit, pk->block, pk->count);
endpacket(pk->unit, TUE_PARO, pk->count, 0);
return;
}
// fake a write time
delay_ms(tudelay[timing].write);
}
// success if we get here
endpacket(pk->unit, TUE_SUCC, pk->count, 0);
return;
}
//
// decode and execute control packets
//
static void command (int8_t flag)
{
tu_cmdpkt pk;
timespec_t time_start;
timespec_t time_end;
char *name= "none";
uint8_t mode = 0;
pk.flag = flag;
pk.length = devrxget();
// check control packet length ... if too long flush it
if (pk.length > sizeof(tu_cmdpkt)) {
error("bad length 0x%02X in cmd packet", pk.length);
reinit();
return;
}
// check packet checksum ... if bad error it
if (getpacket((tu_packet *)&pk)) {
error("cmd checksum error");
endpacket(pk.unit, TUE_DERR, 0, 0);
return;
}
if (debug) info("opcode=0x%02X length=0x%02X", pk.opcode, pk.length);
// dump command if requested
if (verbose) {
// parse commands to classes
switch (pk.opcode) {
case TUO_DIAGNOSE: name = "diagnose"; mode = 1; break;
case TUO_GETCHAR: name = "getchar"; mode = 1; break;
case TUO_INIT: name = "init"; mode = 1; break;
case TUO_NOP: name = "nop"; mode = 1; break;
case TUO_GETSTATUS: name = "getstat"; mode = 1; break;
case TUO_SETSTATUS: name = "setstat"; mode = 1; break;
case TUO_SEEK: name = "seek"; mode = 2; break;
case TUO_READ: name = "read"; mode = 3; break;
case TUO_WRITE: name = "write"; mode = 3; break;
default: name = "unknown"; mode = 3; break;
}
// dump data
switch (mode) {
case 0:
info("%-8s", name);
break;
case 1:
info("%-8s unit=%d", name, pk.unit);
break;
case 2:
info("%-8s unit=%d sw=0x%02X mod=0x%02X blk=0x%04X",
name, pk.unit, pk.switches, pk.modifier, pk.block);
break;
case 3:
info("%-8s unit=%d sw=0x%02X mod=0x%02X blk=0x%04X cnt=0x%04X",
name, pk.unit, pk.switches, pk.modifier, pk.block, pk.count);
break;
}
// get start time of processing
clock_gettime(CLOCK_REALTIME, &time_start);
}
// if we are MRSP capable, look at the switches
if (mrspen) mrsp = (pk.switches & TUS_MRSP) ? 1 : 0;
// decode packet
switch (pk.opcode) {
case TUO_READ: // read data from tu58
turead(&pk);
break;
case TUO_WRITE: // write data to tu58
tuwrite(&pk);
break;
case TUO_SEEK: // reposition tu58
tuseek(&pk);
break;
case TUO_DIAGNOSE: // diagnose packet
delay_ms(tudelay[timing].test);
endpacket(pk.unit, TUE_SUCC, 0, 0);
break;
case TUO_GETCHAR: // get characteristics packet
delay_ms(tudelay[timing].nop);
if (mrspen) {
// MRSP capable just sends the end packet
endpacket(pk.unit, TUE_SUCC, 0, 0);
} else {
// MRSP detect mode not enabled
// indicate we are not MRSP capable
tu_datpkt dk;
dk.flag = TUF_DATA;
dk.length = TU_CHAR_LEN;
bzero(dk.data, dk.length);
putpacket((tu_packet *)&dk);
}
break;
case TUO_INIT: // init packet
delay_ms(tudelay[timing].init);
devtxinit();
devrxinit();
endpacket(pk.unit, TUE_SUCC, 0, 0);
break;
case TUO_NOP: // nop packet
case TUO_GETSTATUS: // get status packet
case TUO_SETSTATUS: // set status packet
delay_ms(tudelay[timing].nop);
endpacket(pk.unit, TUE_SUCC, 0, 0);
break;
default: // unknown packet
delay_ms(tudelay[timing].nop);
endpacket(pk.unit, TUE_BADO, 0, 0);
break;
}
if (verbose) {
uint32_t delta;
// get end time of processing
clock_gettime(CLOCK_REALTIME, &time_end);
// compute elapsed time in milliseconds
delta = 1000L*(time_end.tv_sec - time_start.tv_sec) + (time_end.tv_nsec - time_start.tv_nsec)/1000000L;
if (delta == 0) delta = 1;
// print elapsed time in milliseconds
if (debug) info("%-8s time=%dms", name, delta);
}
return;
}
//
// field requests from host
//
static void* run (void* none)
{
uint8_t flag = TUF_NULL;
uint8_t last = TUF_NULL;
// some init
reinit(); // empty serial line buffers
doinit = !nosync; // start sending init flags?
// say hello
info("emulator %sstarted", runonce++ ? "re" : "");
// loop forever ... almost
for (;;) {
// loop while no characters are available
while (devrxavail() == 0) {
// send INITs if still required
if (doinit) {
if (debug) fprintf(stderr, ".");
devtxput(TUF_INIT);
devtxflush();
delay_ms(75);
}
delay_ms(25);
}
doinit = 0; // quit sending init flags
// process received characters
last = flag;
flag = devrxget();
if (debug) info("flag=0x%02X last=0x%02X", flag, last);
switch (flag) {
case TUF_CTRL:
// control packet - process
command(flag);
break;
case TUF_INIT:
// init flag
if (debug) info("<INIT> seen");
if (last == TUF_INIT) {
// two in a row is special
delay_ms(tudelay[timing].init);
devtxput(TUF_CONT); // send 'continue'
devtxflush(); // send immediate
flag = -1; // undefined
if (debug) info("<INIT><INIT> seen, sending <CONT>");
}
break;
case TUF_BOOT:
// special boot sequence
if (debug) info("<BOOT> seen");
bootio();
break;
case TUF_NULL:
// ignore nulls (which are BREAKs)
if (debug) info("<NULL> seen");
break;
case TUF_CONT:
// continue restarts output
if (debug) info("<CONT> seen, starting output");
devtxstart();
break;
case TUF_XOFF:
// send disable flag stops output
if (debug) info("<XOFF> seen, stopping output");
devtxstop();
break;
case TUF_DATA:
// data packet - should never see one here
error("protocol error - data flag out of sequence");
reinit();
break;
default:
// whoops, protocol error
error("unknown packet flag 0x%02X (%c)", flag, isprint(flag)?flag:'.');
break;
} // switch (flag)
} // for (;;)
return (void*)0;
}
//
// 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
//
void tu58drive (void)
{
// a sanity check for blocksize definition
if (BLOCKSIZE % TU_DATA_LEN != 0)
fatal("illegal BLOCKSIZE (%d) / TU_DATA_LEN (%d) ratio", BLOCKSIZE, TU_DATA_LEN);
// say hello
info("TU58 emulation 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;
// get char from stdin (if available)
if ((c = toupper(conget())) > 0) {
if (c == 'V') {
// toggle verbosity
verbose ^= 1; debug = 0;
info("verbosity set to %s; debug %s",
verbose ? "ON" : "OFF", debug ? "ON" : "OFF");
} else if (c == 'D') {
// toggle debug
verbose = 1; debug ^= 1;
info("verbosity set to %s; debug %s",
verbose ? "ON" : "OFF", debug ? "ON" : "OFF");
} else if (c == 'S') {
// toggle sending init string
doinit = (doinit+1)%2;
if (debug) fprintf(stderr, "\n");
info("send of <INIT> %sabled", doinit ? "en" : "dis");
} else if (c == 'R') {
// kill and restart the emulator
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");
} 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;
}
}
// wait a bit
delay_ms(25);
} // for (;;)
// wait for emulator to finish
if (pthread_join(th_run, NULL))
error("unable to join on emulation thread");
// all done
info("TU58 emulation end");
return;
}
// the end

BIN
tu58em.exe Normal file

Binary file not shown.

BIN
tu58ew.exe Normal file

Binary file not shown.