tu58 drive emulator
v1.4j
This commit is contained in:
parent
cca01f176b
commit
3c387d5edd
106
common.h
Normal file
106
common.h
Normal 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
367
file.c
Normal 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
224
main.c
Normal 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
60
makefile
Normal 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
623
serial.c
Normal 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
127
tu58.h
Normal 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
884
tu58drive.c
Normal 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
BIN
tu58em.exe
Normal file
Binary file not shown.
BIN
tu58ew.exe
Normal file
BIN
tu58ew.exe
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user