/* sigma_dk.c: 7250/7251-7252 cartridge disk simulator | |
Copyright (c) 2007-2008, 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 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
CONNECTION 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. | |
dk 7250/7251-7252 cartridge disk | |
Transfers are always done a sector at a time. | |
*/ | |
#include "sigma_io_defs.h" | |
#include <math.h> | |
#define UNIT_V_HWLK (UNIT_V_UF + 0) /* hwre write lock */ | |
#define UNIT_HWLK (1u << UNIT_V_HWLK) | |
#define UNIT_WPRT (UNIT_HWLK|UNIT_RO) /* write prot */ | |
#define UTRK u3 /* current track */ | |
/* Constants */ | |
#define DK_NUMDR 8 /* drives/ctlr */ | |
#define DK_WDSC 90 /* words/sector */ | |
#define DK_SCTK 16 /* sectors/track */ | |
#define DK_TKUN 408 /* tracks/unit */ | |
#define DK_WDUN (DK_WDSC*DK_SCTK*DK_TKUN) /* words/unit */ | |
/* Address bytes */ | |
#define DKA_V_TK 4 /* track offset */ | |
#define DKA_M_TK 0x1FF | |
#define DKA_V_SC 0 /* sector offset */ | |
#define DKA_M_SC 0xF | |
#define DKA_GETTK(x) (((x) >> DKA_V_TK) & DKA_M_TK) | |
#define DKA_GETSC(x) (((x) >> DKA_V_SC) & DKA_M_SC) | |
/* Status byte 3 is current sector */ | |
#define DKS_NBY 3 | |
/* Device state */ | |
#define DKS_INIT 0x101 | |
#define DKS_END 0x102 | |
#define DKS_WRITE 0x01 | |
#define DKS_READ 0x02 | |
#define DKS_SEEK 0x03 | |
#define DKS_SEEK2 0x103 | |
#define DKS_SENSE 0x04 | |
#define DKS_CHECK 0x05 | |
#define DKS_RDEES 0x12 | |
#define DKS_TEST 0x13 | |
/* Device status */ | |
#define DKV_OVR 0x80 /* overrun - NI */ | |
#define DKV_BADS 0x20 /* bad track */ | |
#define DKV_WPE 0x10 | |
#define GET_PSC(x) ((int32) fmod (sim_gtime() / ((double) (x * DK_WDSC)), \ | |
((double) DK_SCTK))) | |
uint32 dk_cmd = 0; /* state */ | |
uint32 dk_flags = 0; /* status flags */ | |
uint32 dk_ad = 0; /* disk address */ | |
uint32 dk_time = 5; /* inter-word time */ | |
uint32 dk_stime = 20; /* inter-track time */ | |
uint32 dk_stopioe = 1; /* stop on I/O error */ | |
extern uint32 chan_ctl_time; | |
uint32 dk_disp (uint32 op, uint32 dva, uint32 *dvst); | |
uint32 dk_tio_status (uint32 un); | |
uint32 dk_tdv_status (uint32 un); | |
t_stat dk_chan_err (uint32 st); | |
t_stat dk_svc (UNIT *uptr); | |
t_stat dk_reset (DEVICE *dptr); | |
t_bool dk_inv_ad (uint32 *da); | |
t_bool dk_inc_ad (void); | |
t_bool dk_end_sec (UNIT *uptr, uint32 lnt, uint32 exp, uint32 st); | |
/* DK data structures | |
dk_dev DK device descriptor | |
dk_unit DK unit descriptor | |
dk_reg DK register list | |
*/ | |
dib_t dk_dib = { DVA_DK, &dk_disp }; | |
UNIT dk_unit[] = { | |
{ UDATA (&dk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_BUFABLE+UNIT_MUSTBUF, DK_WDUN) }, | |
{ UDATA (&dk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_BUFABLE+UNIT_MUSTBUF, DK_WDUN) }, | |
{ UDATA (&dk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_BUFABLE+UNIT_MUSTBUF, DK_WDUN) }, | |
{ UDATA (&dk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_BUFABLE+UNIT_MUSTBUF, DK_WDUN) }, | |
{ UDATA (&dk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_BUFABLE+UNIT_MUSTBUF, DK_WDUN) }, | |
{ UDATA (&dk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_BUFABLE+UNIT_MUSTBUF, DK_WDUN) }, | |
{ UDATA (&dk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_BUFABLE+UNIT_MUSTBUF, DK_WDUN) }, | |
{ UDATA (&dk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_BUFABLE+UNIT_MUSTBUF, DK_WDUN) } | |
}; | |
REG dk_reg[] = { | |
{ HRDATA (CMD, dk_cmd, 9) }, | |
{ HRDATA (FLAGS, dk_flags, 8) }, | |
{ HRDATA (ADDR, dk_ad, 8) }, | |
{ DRDATA (TIME, dk_time, 24), PV_LEFT+REG_NZ }, | |
{ DRDATA (STIME, dk_stime, 24), PV_LEFT+REG_NZ }, | |
{ FLDATA (STOPIOE, dk_stopioe, 0) }, | |
{ HRDATA (DEVNO, dk_dib.dva, 12), REG_HRO }, | |
{ NULL } | |
}; | |
MTAB dk_mod[] = { | |
{ UNIT_HWLK, 0, "write enabled", "WRITEENABLED", NULL }, | |
{ UNIT_HWLK, UNIT_HWLK, "write locked", "LOCKED", NULL }, | |
{ MTAB_XTD|MTAB_VDV, 0, "CHAN", "CHAN", | |
&io_set_dvc, &io_show_dvc, NULL }, | |
{ MTAB_XTD|MTAB_VDV, 0, "DVA", "DVA", | |
&io_set_dva, &io_show_dva, NULL }, | |
{ MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "CSTATE", NULL, | |
NULL, &io_show_cst, NULL }, | |
{ 0 } | |
}; | |
DEVICE dk_dev = { | |
"DK", dk_unit, dk_reg, dk_mod, | |
DK_NUMDR, 16, 22, 1, 16, 32, | |
NULL, NULL, &dk_reset, | |
NULL, NULL, NULL, | |
&dk_dib, DEV_DISABLE | |
}; | |
/* DK: IO dispatch routine */ | |
uint32 dk_disp (uint32 op, uint32 dva, uint32 *dvst) | |
{ | |
uint32 i; | |
uint32 un = DVA_GETUNIT (dva); | |
UNIT *uptr; | |
if ((un >= DK_NUMDR) || /* inv unit num? */ | |
(dk_unit[un].flags & UNIT_DIS)) /* disabled unit? */ | |
return DVT_NODEV; | |
switch (op) { /* case on op */ | |
case OP_SIO: /* start I/O */ | |
*dvst = dk_tio_status (un); /* get status */ | |
if ((*dvst & (DVS_CST|DVS_DST)) == 0) { /* ctrl + dev idle? */ | |
dk_cmd = DKS_INIT; /* start dev thread */ | |
sim_activate (&dk_unit[un], chan_ctl_time); | |
} | |
break; | |
case OP_TIO: /* test status */ | |
*dvst = dk_tio_status (un); /* return status */ | |
break; | |
case OP_TDV: /* test status */ | |
*dvst = dk_tdv_status (un); /* return status */ | |
break; | |
case OP_HIO: /* halt I/O */ | |
chan_clr_chi (dk_dib.dva); /* clr int */ | |
*dvst = dk_tio_status (un); /* get status */ | |
if ((*dvst & DVS_CST) != 0) { /* ctrl busy? */ | |
for (i = 0; i < DK_NUMDR; i++) { /* find busy unit */ | |
uptr = &dk_unit[i]; | |
if (sim_is_active (uptr)) { /* active? */ | |
sim_cancel (uptr); /* stop */ | |
chan_uen (dk_dib.dva); /* uend */ | |
} /* end if active */ | |
} /* end for */ | |
} | |
break; | |
case OP_AIO: /* acknowledge int */ | |
chan_clr_chi (dk_dib.dva); /* clr int */ | |
*dvst = dk_tdv_status (un); /* status like TDV */ | |
break; | |
default: | |
*dvst = 0; | |
return SCPE_IERR; | |
} | |
return 0; | |
} | |
/* Unit service */ | |
t_stat dk_svc (UNIT *uptr) | |
{ | |
uint32 i, sc, da, wd, wd1, cmd, c[3]; | |
uint32 *fbuf = (uint32 *) uptr->filebuf; | |
int32 t, dc; | |
uint32 st; | |
switch (dk_cmd) { | |
case DKS_INIT: /* init state */ | |
st = chan_get_cmd (dk_dib.dva, &cmd); /* get command */ | |
if (CHS_IFERR (st)) /* channel error? */ | |
return dk_chan_err (st); | |
if ((cmd == 0) || /* invalid cmd? */ | |
((cmd > DKS_CHECK) && (cmd != DKS_RDEES) && (cmd != DKS_TEST))) { | |
chan_uen (dk_dib.dva); /* uend */ | |
return SCPE_OK; | |
} | |
dk_flags = 0; /* clear status */ | |
dk_cmd = cmd & 0x17; /* next state */ | |
if ((cmd == DKS_SEEK) || /* fast cmd? */ | |
(cmd == DKS_SENSE) || | |
(cmd == DKS_TEST)) | |
sim_activate (uptr, chan_ctl_time); /* schedule soon */ | |
else { /* data transfer */ | |
sc = DKA_GETSC (dk_ad); /* new sector */ | |
t = sc - GET_PSC (dk_time); /* delta to new */ | |
if (t < 0) /* wrap around? */ | |
t = t + DK_SCTK; | |
sim_activate (uptr, t * dk_time * DK_WDSC); /* schedule op */ | |
} | |
return SCPE_OK; | |
case DKS_END: /* end state */ | |
st = chan_end (dk_dib.dva); /* set channel end */ | |
if (CHS_IFERR (st)) /* channel error? */ | |
return dk_chan_err (st); | |
if (st == CHS_CCH) { /* command chain? */ | |
dk_cmd = DKS_INIT; /* restart thread */ | |
sim_activate (uptr, chan_ctl_time); | |
} | |
return SCPE_OK; /* done */ | |
case DKS_SEEK: /* seek */ | |
c[0] = c[1] = 0; | |
for (i = 0, st = 0; (i < 2) && (st != CHS_ZBC); i++) { | |
st = chan_RdMemB (dk_dib.dva, &c[i]); /* get byte */ | |
if (CHS_IFERR (st)) /* channel error? */ | |
return dk_chan_err (st); | |
} | |
dk_ad = ((c[0] & 0x7F) << 8) | c[1]; /* new address */ | |
if (((i != 2) || (st != CHS_ZBC)) && /* length error? */ | |
chan_set_chf (dk_dib.dva, CHF_LNTE)) /* care? */ | |
return SCPE_OK; | |
dc = DKA_GETTK (dk_ad); /* desired track */ | |
t = abs (uptr->UTRK - dc); /* get track diff */ | |
if (t == 0) | |
t = 1; | |
sim_activate (uptr, t * dk_stime); | |
uptr->UTRK = dc; /* put on track */ | |
dk_cmd = DKS_SEEK2; | |
return SCPE_OK; | |
case DKS_SEEK2: /* seek complete */ | |
if (uptr->UTRK >= DK_TKUN) { | |
dk_flags |= DKV_BADS; | |
chan_uen (dk_dib.dva); | |
return SCPE_OK; | |
} | |
break; /* seek done */ | |
case DKS_SENSE: /* sense */ | |
c[0] = ((dk_ad >> 8) & 0x7F) | ((uptr->flags & UNIT_RO)? 0x80: 0); | |
c[1] = dk_ad & 0xFF; /* address */ | |
c[2] = GET_PSC (dk_time); /* curr sector */ | |
for (i = 0, st = 0; (i < DKS_NBY) && (st != CHS_ZBC); i++) { | |
st = chan_WrMemB (dk_dib.dva, c[i]); /* store char */ | |
if (CHS_IFERR (st)) /* channel error? */ | |
return dk_chan_err (st); | |
} | |
if (((i != DKS_NBY) || (st != CHS_ZBC)) && | |
chan_set_chf (dk_dib.dva, CHF_LNTE)) /* length error? */ | |
return SCPE_OK; | |
break; | |
case DKS_WRITE: /* write */ | |
if (uptr->flags & UNIT_RO) { /* write locked? */ | |
dk_flags |= DKV_WPE; /* set status */ | |
chan_uen (dk_dib.dva); /* uend */ | |
return SCPE_OK; | |
} | |
if (dk_inv_ad (&da)) { /* invalid addr? */ | |
chan_uen (dk_dib.dva); /* uend */ | |
return SCPE_OK; | |
} | |
for (i = 0, st = 0; i < DK_WDSC; da++, i++) { /* sector loop */ | |
if (st != CHS_ZBC) { /* chan not done? */ | |
st = chan_RdMemW (dk_dib.dva, &wd); /* read word */ | |
if (CHS_IFERR (st)) { /* channel error? */ | |
dk_inc_ad (); /* da increments */ | |
return dk_chan_err (st); | |
} | |
} | |
else wd = 0; | |
fbuf[da] = wd; /* store in buffer */ | |
if (da >= uptr->hwmark) /* update length */ | |
uptr->hwmark = da + 1; | |
} | |
if (dk_end_sec (uptr, i, DK_WDSC, st)) /* transfer done? */ | |
return SCPE_OK; /* err or cont */ | |
break; | |
/* Must be done by bytes to get precise miscompare */ | |
case DKS_CHECK: /* write check */ | |
if (dk_inv_ad (&da)) { /* invalid addr? */ | |
chan_uen (dk_dib.dva); /* uend */ | |
return SCPE_OK; | |
} | |
for (i = 0, st = 0; (i < (DK_WDSC * 4)) && (st != CHS_ZBC); ) { | |
st = chan_RdMemB (dk_dib.dva, &wd); /* read byte */ | |
if (CHS_IFERR (st)) { /* channel error? */ | |
dk_inc_ad (); /* da increments */ | |
return dk_chan_err (st); | |
} | |
wd1 = (fbuf[da] >> (24 - ((i % 4) * 8))) & 0xFF; /* byte */ | |
if (wd != wd1) { /* check error? */ | |
dk_inc_ad (); /* da increments */ | |
chan_set_chf (dk_dib.dva, CHF_XMDE); /* set xmt err flag */ | |
chan_uen (dk_dib.dva); /* force uend */ | |
return SCPE_OK; | |
} | |
da = da + ((++i % 4) == 0); /* every 4th byte */ | |
} | |
if (dk_end_sec (uptr, i, DK_WDSC * 4, st)) /* transfer done? */ | |
return SCPE_OK; /* err or cont */ | |
break; | |
case DKS_READ: /* read */ | |
if (dk_inv_ad (&da)) { /* invalid addr? */ | |
chan_uen (dk_dib.dva); /* uend */ | |
return SCPE_OK; | |
} | |
for (i = 0, st = 0; (i < DK_WDSC) && (st != CHS_ZBC); da++, i++) { | |
st = chan_WrMemW (dk_dib.dva, fbuf[da]); /* store in mem */ | |
if (CHS_IFERR (st)) { /* channel error? */ | |
dk_inc_ad (); /* da increments */ | |
return dk_chan_err (st); | |
} | |
} | |
if (dk_end_sec (uptr, i, DK_WDSC, st)) /* transfer done? */ | |
return SCPE_OK; /* err or cont */ | |
break; | |
} | |
dk_cmd = DKS_END; /* op done, next state */ | |
sim_activate (uptr, chan_ctl_time); | |
return SCPE_OK; | |
} | |
/* Common read/write sector end routine | |
case 1 - more to transfer, not end disk - reschedule, return TRUE | |
case 2 - more to transfer, end disk - uend, return TRUE | |
case 3 - transfer done, length error - uend, return TRUE | |
case 4 - transfer done, no length error - return FALSE (sched end state) | |
*/ | |
t_bool dk_end_sec (UNIT *uptr, uint32 lnt, uint32 exp, uint32 st) | |
{ | |
if (st != CHS_ZBC) { /* end record? */ | |
if (dk_inc_ad ()) /* inc addr, ovf? */ | |
chan_uen (dk_dib.dva); /* uend */ | |
else sim_activate (uptr, dk_time * 16); /* no, next sector */ | |
return TRUE; | |
} | |
dk_inc_ad (); /* just incr addr */ | |
if ((lnt != exp) && /* length error? */ | |
chan_set_chf (dk_dib.dva, CHF_LNTE)) /* do we care? */ | |
return TRUE; | |
return FALSE; /* cmd done */ | |
} | |
/* DK status routine */ | |
uint32 dk_tio_status (uint32 un) | |
{ | |
uint32 i; | |
for (i = 0; i < DK_NUMDR; i++) { /* loop thru units */ | |
if (sim_is_active (&dk_unit[i])) /* active? */ | |
return (DVS_AUTO | DVS_CBUSY | (CC2 << DVT_V_CC) | | |
((i == un)? DVS_DBUSY: 0)); | |
} | |
return DVS_AUTO; | |
} | |
uint32 dk_tdv_status (uint32 un) | |
{ | |
return dk_flags | (dk_inv_ad (NULL)? DKV_BADS: 0); | |
} | |
/* Validate disk address */ | |
t_bool dk_inv_ad (uint32 *da) | |
{ | |
uint32 tk = DKA_GETTK (dk_ad); | |
uint32 sc = DKA_GETSC (dk_ad); | |
if (tk >= DK_TKUN) /* badtrk? */ | |
return TRUE; | |
if (da) /* return word addr */ | |
*da = ((tk * DK_SCTK) + sc) * DK_WDSC; | |
return FALSE; | |
} | |
/* Increment disk address */ | |
t_bool dk_inc_ad (void) | |
{ | |
uint32 tk = DKA_GETTK (dk_ad); | |
uint32 sc = DKA_GETSC (dk_ad); | |
sc = sc + 1; /* sector++ */ | |
if (sc >= DK_SCTK) { /* overflow? */ | |
sc = 0; /* wrap sector */ | |
tk = tk + 1; /* track++ */ | |
} | |
dk_ad = ((tk << DKA_V_TK) | /* rebuild dk_ad */ | |
(sc << DKA_V_SC)); | |
if (tk >= DK_TKUN) /* invalid addr? */ | |
return TRUE; | |
return FALSE; | |
} | |
/* Channel error */ | |
t_stat dk_chan_err (uint32 st) | |
{ | |
chan_uen (dk_dib.dva); /* uend */ | |
if (st < CHS_ERR) | |
return st; | |
return SCPE_OK; | |
} | |
/* Reset routine */ | |
t_stat dk_reset (DEVICE *dptr) | |
{ | |
uint32 i; | |
for (i = 0; i < DK_NUMDR; i++) { | |
sim_cancel (&dk_unit[i]); /* stop dev thread */ | |
dk_unit[i].UTRK = 0; | |
} | |
dk_cmd = 0; | |
dk_flags = 0; | |
dk_ad = 0; | |
chan_reset_dev (dk_dib.dva); /* clr int, active */ | |
return SCPE_OK; | |
} |