blob: c7db6abf0b1425920423ac773a4ba1d6e2510655 [file] [log] [blame] [raw]
/* pdp11_ta.c: PDP-11 cassette tape simulator
Copyright (c) 2007, Robert M Supnik
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ATAION OF CONTRATA, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNETAION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of Robert M Supnik shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from Robert M Supnik.
ta TA11/TU60 cassette tape
06-Aug-07 RMS Foward op at BOT skips initial file gap
Magnetic tapes are represented as a series of variable records
of the form:
32b byte count
byte 0
byte 1
:
byte n-2
byte n-1
32b byte count
If the byte count is odd, the record is padded with an extra byte
of junk. File marks are represented by a byte count of 0.
Cassette format differs in one very significant way: it has file gaps
rather than file marks. If the controller spaces or reads into a file
gap and then reverses direction, the file gap is not seen again. This
is in contrast to magnetic tapes, where the file mark is a character
sequence and is seen again if direction is reversed. In addition,
cassettes have an initial file gap which is automatically skipped on
forward operations from beginning of tape.
*/
#include "pdp11_defs.h"
#include "sim_tape.h"
#define TA_NUMDR 2 /* #drives */
#define FNC u3 /* unit function */
#define UST u4 /* unit status */
#define TA_SIZE 93000 /* chars/tape */
#define TA_MAXFR (TA_SIZE) /* max record lnt */
/* Control/status - TACS */
#define TACS_ERR (1 << CSR_V_ERR) /* error */
#define TACS_CRC 0040000 /* CRC */
#define TACS_BEOT 0020000 /* BOT/EOT */
#define TACS_WLK 0010000 /* write lock */
#define TACS_EOF 0004000 /* end file */
#define TACS_TIM 0002000 /* timing */
#define TACS_EMP 0001000 /* empty */
#define TACS_V_UNIT 8 /* unit */
#define TACS_M_UNIT (TA_NUMDR - 1)
#define TACS_UNIT (TACS_M_UNIT << TACS_V_UNIT)
#define TACS_TR (1 << CSR_V_DONE) /* transfer req */
#define TACS_IE (1 << CSR_V_IE) /* interrupt enable */
#define TACS_RDY 0000040 /* ready */
#define TACS_ILBS 0000020 /* start CRC */
#define TACS_V_FNC 1 /* function */
#define TACS_M_FNC 07
#define TACS_WFG 00
#define TACS_WRITE 01
#define TACS_READ 02
#define TACS_SRF 03
#define TACS_SRB 04
#define TACS_SFF 05
#define TACS_SFB 06
#define TACS_REW 07
#define TACS_2ND 010
#define TACS_3RD 030
#define TACS_FNC (TACS_M_FNC << TACS_V_FNC)
#define TACS_GO (1 << CSR_V_GO) /* go */
#define TACS_W (TACS_UNIT|TACS_IE|TACS_ILBS|TACS_FNC)
#define TACS_XFRERR (TACS_ERR|TACS_CRC|TACS_WLK|TACS_EOF|TACS_TIM)
#define GET_UNIT(x) (((x) >> TACS_V_UNIT) & TACS_M_UNIT)
#define GET_FNC(x) (((x) >> TACS_V_FNC) & TACS_M_FNC)
/* Function code flags */
#define OP_WRI 01 /* op is a write */
#define OP_REV 02 /* op is rev motion */
#define OP_FWD 04 /* op is fwd motion */
/* Unit status flags */
#define UST_REV (OP_REV) /* last op was rev */
#define UST_GAP 01 /* last op hit gap */
extern int32 int_req[IPL_HLVL];
extern FILE *sim_deb;
uint32 ta_cs = 0; /* control/status */
uint32 ta_idb = 0; /* input data buf */
uint32 ta_odb = 0; /* output data buf */
uint32 ta_write = 0; /* TU60 write flag */
uint32 ta_bptr = 0; /* buf ptr */
uint32 ta_blnt = 0; /* buf length */
int32 ta_stime = 1000; /* start time */
int32 ta_ctime = 100; /* char latency */
uint32 ta_stopioe = 1; /* stop on error */
uint8 *ta_xb = NULL; /* transfer buffer */
static uint8 ta_fnc_tab[TACS_M_FNC + 1] = {
OP_WRI|OP_FWD, OP_WRI|OP_FWD, OP_FWD, OP_REV,
OP_REV , OP_FWD, OP_FWD, 0
};
DEVICE ta_dev;
t_stat ta_rd (int32 *data, int32 PA, int32 access);
t_stat ta_wr (int32 data, int32 PA, int32 access);
t_stat ta_svc (UNIT *uptr);
t_stat ta_reset (DEVICE *dptr);
t_stat ta_attach (UNIT *uptr, char *cptr);
t_stat ta_detach (UNIT *uptr);
void ta_go (void);
t_stat ta_map_err (UNIT *uptr, t_stat st);
UNIT *ta_busy (void);
void ta_set_tr (void);
uint32 ta_updsta (UNIT *uptr);
uint32 ta_crc (uint8 *buf, uint32 cnt);
/* TA data structures
ta_dev TA device descriptor
ta_unit TA unit list
ta_reg TA register list
ta_mod TA modifier list
*/
DIB ta_dib = {
IOBA_TA, IOLN_TA, &ta_rd, &ta_wr,
1, IVCL (TA), VEC_TA, { NULL }
};
UNIT ta_unit[] = {
{ UDATA (&ta_svc, UNIT_ATTABLE+UNIT_ROABLE, TA_SIZE) },
{ UDATA (&ta_svc, UNIT_ATTABLE+UNIT_ROABLE, TA_SIZE) },
};
REG ta_reg[] = {
{ ORDATA (TACS, ta_cs, 16) },
{ ORDATA (TAIDB, ta_idb, 8) },
{ ORDATA (TAODB, ta_odb, 8) },
{ FLDATA (WRITE, ta_write, 0) },
{ FLDATA (INT, IREQ (TA), INT_V_TA) },
{ FLDATA (ERR, ta_cs, CSR_V_ERR) },
{ FLDATA (TR, ta_cs, CSR_V_DONE) },
{ FLDATA (IE, ta_cs, CSR_V_IE) },
{ DRDATA (BPTR, ta_bptr, 17) },
{ DRDATA (BLNT, ta_blnt, 17) },
{ DRDATA (STIME, ta_stime, 24), PV_LEFT + REG_NZ },
{ DRDATA (CTIME, ta_ctime, 24), PV_LEFT + REG_NZ },
{ FLDATA (STOP_IOE, ta_stopioe, 0) },
{ URDATA (UFNC, ta_unit[0].FNC, 8, 5, 0, TA_NUMDR, 0), REG_HRO },
{ URDATA (UST, ta_unit[0].UST, 8, 2, 0, TA_NUMDR, 0), REG_HRO },
{ URDATA (POS, ta_unit[0].pos, 10, T_ADDR_W, 0,
TA_NUMDR, PV_LEFT | REG_RO) },
{ ORDATA (DEVADDR, ta_dib.ba, 32), REG_HRO },
{ ORDATA (DEVVEC, ta_dib.vec, 16), REG_HRO },
{ NULL }
};
MTAB ta_mod[] = {
{ MTUF_WLK, 0, "write enabled", "WRITEENABLED", NULL },
{ MTUF_WLK, MTUF_WLK, "write locked", "LOCKED", NULL },
// { MTAB_XTD|MTAB_VUN, 0, "FORMAT", "FORMAT",
// &sim_tape_set_fmt, &sim_tape_show_fmt, NULL },
{ MTAB_XTD|MTAB_VUN, 0, "CAPACITY", NULL,
NULL, &sim_tape_show_capac, NULL },
{ MTAB_XTD|MTAB_VDV, IOLN_TA, "ADDRESS", "ADDRESS",
&set_addr, &show_addr, NULL },
{ MTAB_XTD|MTAB_VDV, 0, "VECTOR", "VECTOR",
&set_vec, &show_vec, NULL },
{ 0 }
};
DEVICE ta_dev = {
"TA", ta_unit, ta_reg, ta_mod,
TA_NUMDR, 10, 31, 1, 8, 8,
NULL, NULL, &ta_reset,
NULL, &ta_attach, &ta_detach,
&ta_dib, DEV_DISABLE | DEV_DIS | DEV_DEBUG
};
/* I/O dispatch routines, I/O addresses 17777500 - 17777503
17777500 TACS read/write
17777502 TADB read/write
*/
t_stat ta_rd (int32 *data, int32 PA, int32 access)
{
switch ((PA >> 1) & 01) { /* decode PA<1> */
case 0: /* TACSR */
*data = ta_updsta (NULL); /* update status */
break;
case 1: /* TADB */
*data = ta_idb; /* return byte */
ta_cs &= ~TACS_TR; /* clear tra req */
ta_updsta (NULL);
break;
}
return SCPE_OK;
}
t_stat ta_wr (int32 data, int32 PA, int32 access)
{
switch ((PA >> 1) & 01) { /* decode PA<1> */
case 0: /* TACS */
if (access == WRITEB) data = (PA & 1)? /* byte write? */
(ta_cs & 0377) | (data << 8): /* merge old */
(ta_cs & ~0377) | data;
ta_cs = (ta_cs & ~TACS_W) | (data & TACS_W); /* merge new */
if ((data & CSR_GO) && !ta_busy ()) /* go, not busy? */
ta_go (); /* start operation */
if (ta_cs & TACS_ILBS) ta_cs &= ~TACS_TR; /* ILBS inhibits TR */
break;
case 1: /* TADB */
if (PA & 1) break; /* ignore odd byte */
ta_odb = data; /* return byte */
ta_cs &= ~TACS_TR; /* clear tra req */
break;
} /* end switch */
ta_updsta (NULL); /* update status */
return SCPE_OK;
}
/* Start a new operation - cassette is not busy */
void ta_go (void)
{
UNIT *uptr = ta_dev.units + GET_UNIT (ta_cs);
uint32 fnc = GET_FNC (ta_cs);
uint32 flg = ta_fnc_tab[fnc];
uint32 old_ust = uptr->UST;
if (DEBUG_PRS (ta_dev)) fprintf (sim_deb,
">>TA start: op=%o, old_sta = %o, pos=%d\n",
fnc, uptr->UST, uptr->pos);
ta_cs &= ~(TACS_XFRERR|TACS_EMP|TACS_TR|TACS_RDY); /* clr err, tr, rdy */
ta_bptr = 0; /* init buffer */
ta_blnt = 0;
if ((uptr->flags & UNIT_ATT) == 0) {
ta_cs |= TACS_ERR|TACS_EMP|TACS_RDY;
return;
}
if (flg & OP_WRI) { /* write op? */
if (sim_tape_wrp (uptr)) { /* locked? */
ta_cs |= TACS_ERR|TACS_WLK|TACS_RDY; /* don't start */
return;
}
ta_odb = 0;
ta_write = 1;
}
else {
ta_idb = 0;
ta_write = 0;
}
ta_cs &= ~TACS_BEOT; /* tape in motion */
uptr->FNC = fnc; /* save function */
if ((fnc != TACS_REW) && !(flg & OP_WRI)) { /* spc/read cmd? */
t_mtrlnt t;
t_stat st;
uptr->UST = flg & UST_REV; /* save direction */
if (sim_tape_bot (uptr) && (flg & OP_FWD)) { /* spc/read fwd bot? */
st = sim_tape_rdrecf (uptr, ta_xb, &t, TA_MAXFR); /* skip file gap */
if (st != MTSE_TMK) /* not there? */
sim_tape_rewind (uptr); /* restore tap pos */
else old_ust = 0; /* defang next */
}
if ((old_ust ^ uptr->UST) == (UST_REV|UST_GAP)) { /* reverse in gap? */
if (uptr->UST) /* skip file gap */
sim_tape_rdrecr (uptr, ta_xb, &t, TA_MAXFR);
else sim_tape_rdrecf (uptr, ta_xb, &t, TA_MAXFR);
if (DEBUG_PRS (ta_dev)) fprintf (sim_deb,
">>TA skip gap: op=%o, old_sta = %o, pos=%d\n",
fnc, uptr->UST, uptr->pos);
}
}
else uptr->UST = 0;
sim_activate (uptr, ta_stime); /* schedule op */
return;
}
/* Unit service */
t_stat ta_svc (UNIT *uptr)
{
uint32 i, crc;
uint32 flg = ta_fnc_tab[uptr->FNC & TACS_M_FNC];
t_mtrlnt tbc;
t_stat st, r;
if ((uptr->flags & UNIT_ATT) == 0) { /* not attached? */
ta_cs |= TACS_ERR|TACS_EMP|TACS_RDY;
ta_updsta (uptr); /* update status */
return (ta_stopioe? SCPE_UNATT: SCPE_OK);
}
if (((flg & OP_FWD) && sim_tape_eot (uptr)) || /* illegal motion? */
((flg & OP_REV) && sim_tape_bot (uptr))) {
ta_cs |= TACS_ERR|TACS_BEOT|TACS_RDY; /* error */
ta_updsta (uptr);
return SCPE_OK;
}
r = SCPE_OK;
switch (uptr->FNC) { /* case on function */
case TACS_READ: /* read start */
st = sim_tape_rdrecf (uptr, ta_xb, &ta_blnt, TA_MAXFR); /* get rec */
if (st == MTSE_RECE) ta_cs |= TACS_ERR|TACS_CRC; /* rec in err? */
else if (st != MTSE_OK) { /* other error? */
r = ta_map_err (uptr, st); /* map error */
break;
}
crc = ta_crc (ta_xb, ta_blnt); /* calculate CRC */
ta_xb[ta_blnt++] = (crc >> 8) & 0377; /* append to buffer */
ta_xb[ta_blnt++] = crc & 0377;
uptr->FNC |= TACS_2ND; /* next state */
sim_activate (uptr, ta_ctime); /* sched next char */
return SCPE_OK;
case TACS_READ|TACS_2ND: /* read char */
if (ta_bptr < ta_blnt) /* more chars? */
ta_idb = ta_xb[ta_bptr++];
else { /* no */
ta_idb = 0;
ta_cs |= TACS_ERR|TACS_CRC; /* overrun */
break; /* tape stops */
}
if (ta_cs & TACS_ILBS) { /* CRC seq? */
uptr->FNC |= TACS_3RD; /* next state */
sim_activate (uptr, ta_stime); /* sched CRC chk */
}
else {
ta_set_tr (); /* set tra req */
sim_activate (uptr, ta_ctime); /* sched next char */
}
return SCPE_OK;
case TACS_READ|TACS_3RD: /* second read CRC */
if (ta_bptr != ta_blnt) { /* partial read? */
crc = ta_crc (ta_xb, ta_bptr + 2); /* actual CRC */
if (crc != 0) ta_cs |= TACS_ERR|TACS_CRC; /* must be zero */
}
break; /* read done */
case TACS_WRITE: /* write start */
for (i = 0; i < TA_MAXFR; i++) ta_xb[i] = 0; /* clear buffer */
ta_set_tr (); /* set tra req */
uptr->FNC |= TACS_2ND; /* next state */
sim_activate (uptr, ta_ctime); /* sched next char */
return SCPE_OK;
case TACS_WRITE|TACS_2ND: /* write char */
if (ta_cs & TACS_ILBS) { /* CRC seq? */
uptr->FNC |= TACS_3RD; /* next state */
sim_activate (uptr, ta_stime); /* sched wri done */
}
else {
if ((ta_bptr < TA_MAXFR) && /* room in buf? */
((uptr->pos + ta_bptr) < uptr->capac)) /* room on tape? */
ta_xb[ta_bptr++] = ta_odb; /* store char */
ta_set_tr (); /* set tra req */
sim_activate (uptr, ta_ctime); /* sched next char */
}
return SCPE_OK;
case TACS_WRITE|TACS_3RD: /* write CRC */
if (ta_bptr) { /* anything to write? */
if (st = sim_tape_wrrecf (uptr, ta_xb, ta_bptr)) /* write, err? */
r = ta_map_err (uptr, st); /* map error */
}
break; /* op done */
case TACS_WFG: /* write file gap */
if (st = sim_tape_wrtmk (uptr)) /* write tmk, err? */
r = ta_map_err (uptr, st); /* map error */
break;
case TACS_REW: /* rewind */
sim_tape_rewind (uptr);
ta_cs |= TACS_BEOT; /* bot, no error */
break;
case TACS_SRB: /* space rev blk */
if (st = sim_tape_sprecr (uptr, &tbc)) /* space rev, err? */
r = ta_map_err (uptr, st); /* map error */
break;
case TACS_SRF: /* space rev file */
while ((st = sim_tape_sprecr (uptr, &tbc)) == MTSE_OK) ;
if (st == MTSE_TMK) /* if tape mark, */
ta_cs |= TACS_EOF; /* set EOF, no err */
else r = ta_map_err (uptr, st); /* else map error */
break;
case TACS_SFB: /* space fwd blk */
if (st = sim_tape_sprecf (uptr, &tbc)) /* space rev, err? */
r = ta_map_err (uptr, st); /* map error */
ta_cs |= TACS_CRC; /* CRC sets, no err */
break;
case TACS_SFF: /* space fwd file */
while ((st = sim_tape_sprecf (uptr, &tbc)) == MTSE_OK) ;
if (st == MTSE_TMK) /* if tape mark, */
ta_cs |= TACS_EOF; /* set EOF, no err */
else r = ta_map_err (uptr, st); /* else map error */
break;
default: /* never get here! */
return SCPE_IERR;
} /* end case */
ta_cs |= TACS_RDY; /* set ready */
ta_updsta (uptr); /* update status */
if (DEBUG_PRS (ta_dev)) fprintf (sim_deb,
">>TA done: op=%o, status = %o, pos=%d\n",
uptr->FNC, ta_cs, uptr->pos);
return r;
}
/* Update controller status */
uint32 ta_updsta (UNIT *uptr)
{
if (uptr == NULL) { /* unit specified? */
if ((uptr = ta_busy ()) == NULL) /* use busy */
uptr = ta_dev.units + GET_UNIT (ta_cs); /* use sel unit */
}
else if (ta_cs & TACS_EOF) uptr->UST |= UST_GAP; /* save EOF */
if (uptr->flags & UNIT_ATT) ta_cs &= ~TACS_EMP; /* attached? */
else ta_cs |= TACS_EMP|TACS_RDY; /* no, empty, ready */
if ((ta_cs & TACS_IE) && /* int enabled? */
(ta_cs & (TACS_TR|TACS_RDY))) /* req or ready? */
SET_INT (TA); /* set int req */
else CLR_INT (TA); /* no, clr int req */
return ta_cs;
}
/* Set transfer request */
void ta_set_tr (void)
{
if (ta_cs & TACS_TR) ta_cs |= (TACS_ERR|TACS_TIM); /* flag still set? */
else ta_cs |= TACS_TR; /* set xfr req */
if (ta_cs & TACS_IE) SET_INT (TA); /* if ie, int req */
return;
}
/* Test if controller busy */
UNIT *ta_busy (void)
{
uint32 u;
UNIT *uptr;
for (u = 0; u < TA_NUMDR; u++) { /* loop thru units */
uptr = ta_dev.units + u;
if (sim_is_active (uptr)) return uptr;
}
return NULL;
}
/* Calculate CRC on buffer */
uint32 ta_crc (uint8 *buf, uint32 cnt)
{
uint32 crc, i, j;
crc = 0;
for (i = 0; i < cnt; i++) {
crc = crc ^ (((uint32) buf[i]) << 8);
for (j = 0; j < 8; j++) {
if (crc & 1) crc = (crc >> 1) ^ 0xA001;
else crc = crc >> 1;
}
}
return crc;
}
/* Map error status */
t_stat ta_map_err (UNIT *uptr, t_stat st)
{
switch (st) {
case MTSE_FMT: /* illegal fmt */
case MTSE_UNATT: /* unattached */
ta_cs |= TACS_ERR|TACS_CRC;
case MTSE_OK: /* no error */
return SCPE_IERR; /* never get here! */
case MTSE_TMK: /* end of file */
ta_cs |= TACS_ERR|TACS_EOF;
break;
case MTSE_IOERR: /* IO error */
ta_cs |= TACS_ERR|TACS_CRC; /* set crc err */
if (ta_stopioe) return SCPE_IOERR;
break;
case MTSE_INVRL: /* invalid rec lnt */
ta_cs |= TACS_ERR|TACS_CRC; /* set crc err */
return SCPE_MTRLNT;
case MTSE_RECE: /* record in error */
case MTSE_EOM: /* end of medium */
ta_cs |= TACS_ERR|TACS_CRC; /* set crc err */
break;
case MTSE_BOT: /* reverse into BOT */
ta_cs |= TACS_ERR|TACS_BEOT; /* set bot */
break;
case MTSE_WRP: /* write protect */
ta_cs |= TACS_ERR|TACS_WLK; /* set wlk err */
break;
}
return SCPE_OK;
}
/* Reset routine */
t_stat ta_reset (DEVICE *dptr)
{
uint32 u;
UNIT *uptr;
ta_cs = 0;
ta_idb = 0;
ta_odb = 0;
ta_write = 0;
ta_bptr = 0;
ta_blnt = 0;
CLR_INT (TA); /* clear interrupt */
for (u = 0; u < TA_NUMDR; u++) { /* loop thru units */
uptr = ta_dev.units + u;
sim_cancel (uptr); /* cancel activity */
sim_tape_reset (uptr); /* reset tape */
}
if (ta_xb == NULL) ta_xb = (uint8 *) calloc (TA_MAXFR + 2, sizeof (uint8));
if (ta_xb == NULL) return SCPE_MEM;
return SCPE_OK;
}
/* Attach routine */
t_stat ta_attach (UNIT *uptr, char *cptr)
{
t_stat r;
r = sim_tape_attach (uptr, cptr);
if (r != SCPE_OK) return r;
ta_updsta (NULL);
uptr->UST = 0;
return r;
}
/* Detach routine */
t_stat ta_detach (UNIT* uptr)
{
t_stat r;
if (!(uptr->flags & UNIT_ATT)) return SCPE_OK; /* check attached */
r = sim_tape_detach (uptr);
ta_updsta (NULL);
uptr->UST = 0;
return r;
}