918 lines
22 KiB
C
918 lines
22 KiB
C
//
|
|
// tu58 - Emulate a TU58 over a serial line
|
|
//
|
|
// Original (C) 1984 Dan Ts'o <Rockefeller Univ. Dept. of Neurobiology>
|
|
// Update (C) 2005-2017 Donald N North <ak6dn_at_mindspring_dot_com>
|
|
//
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions
|
|
// are met:
|
|
//
|
|
// o Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
//
|
|
// o Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
//
|
|
// o Neither the name of the copyright holder nor the names of its
|
|
// contributors may be used to endorse or promote products derived from
|
|
// this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// 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 <setjmp.h>
|
|
|
|
#include "tu58.h"
|
|
|
|
#ifdef MACOSX
|
|
// clock_gettime() is not available under MACOSX
|
|
#define CLOCK_REALTIME 1
|
|
#include <mach/mach_time.h>
|
|
void clock_gettime (int dummy, struct timespec *t) {
|
|
uint64_t mt;
|
|
mt = mach_absolute_time();
|
|
t->tv_sec = mt / 1000000000;
|
|
t->tv_nsec = mt % 1000000000;
|
|
}
|
|
#endif // MACOSX
|
|
|
|
// 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 jmp_buf rx_break_env; // longjmp state for when a BREAK is detected on rx
|
|
|
|
|
|
|
|
//
|
|
// 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 a byte from the host, process BREAK if seen
|
|
//
|
|
static uint8_t rxget (void)
|
|
{
|
|
uint8_t state;
|
|
uint8_t c;
|
|
|
|
// get a byte and state flag
|
|
c = devrxget(&state);
|
|
|
|
// seen a BREAK on the rx line, so abort this packet
|
|
if (state == DEV_BREAK) longjmp(rx_break_env, c);
|
|
|
|
// return the byte
|
|
return c;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// read of boot is not packetized, is just raw data
|
|
//
|
|
static void bootio (void)
|
|
{
|
|
int32_t unit;
|
|
int32_t count;
|
|
uint8_t buffer[TU_BOOT_LEN];
|
|
|
|
// check unit number for validity
|
|
unit = rxget();
|
|
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;
|
|
|
|
// formatted packet dump, but skip it in background mode
|
|
if (!background) {
|
|
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 = rxget();
|
|
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++ = rxget();
|
|
|
|
// 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 = rxget();
|
|
if (debug) info("flag=0x%02X last=0x%02X", dk.flag, last);
|
|
if (last == TUF_INIT && dk.flag == TUF_INIT) {
|
|
// two in a row is special
|
|
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 = rxget();
|
|
|
|
// 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;
|
|
|
|
// avoid uninitialized variable warnings
|
|
time_start.tv_sec = 0;
|
|
time_start.tv_nsec = 0;
|
|
time_end.tv_sec = 0;
|
|
time_end.tv_nsec = 0;
|
|
|
|
pk.flag = flag;
|
|
pk.length = rxget();
|
|
|
|
// 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("TU58 emulator %sstarted", runonce++ ? "re" : "");
|
|
|
|
// loop forever ... almost
|
|
for (;;) {
|
|
|
|
// setup for when a BREAK is detected
|
|
if (setjmp(rx_break_env)) {
|
|
// return here when we get a BREAK on the rx input
|
|
if (debug) info("<BREAK> seen");
|
|
// fall thru to main loop
|
|
}
|
|
|
|
// loop while no characters are available
|
|
while (devrxavail() == 0) {
|
|
// delays and printout only if not VAX
|
|
if (!vax) {
|
|
// 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 = rxget();
|
|
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
|
|
if (!vax) delay_ms(tudelay[timing].init); // no delay for VAX
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// 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 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");
|
|
|
|
// 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_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 end");
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
// the end
|