/* sigma_rad.c: Sigma 7211/7212 or 7231/7232 fixed head 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. | |
rad 7211/7212 or 7231/7232 fixed head disk | |
The RAD is a head-per-track disk. To minimize overhead, the entire RAD | |
is buffered in memory. | |
Transfers are always done a sector at a time. | |
*/ | |
#include "sigma_io_defs.h" | |
#include <math.h> | |
/* Constants */ | |
#define RAD_7212 0 /* ctlr type */ | |
#define RAD_7232 1 | |
#define RAD_NUMDR 4 /* drives/ctlr */ | |
#define RAD_WDSC 256 /* words/sector */ | |
#define RAD_WDMASK (RAD_WDSC - 1) | |
#define RAD_SCTK1 82 /* sectors/track */ | |
#define RAD_SCTK3 12 | |
#define RAD_TKUN1 64 /* tracks/unit */ | |
#define RAD_TKUN3 512 | |
#define RAD_WDUNDF (RAD_WDSC*RAD_SCTK1*RAD_TKUN1) /* dflt words/unit */ | |
#define RAD_WDUN (RAD_WDSC*rad_tab[rad_model].sctk*rad_tab[rad_model].tkun) | |
#define RAD_N_WLK 16 /* num wlk switches */ | |
/* Address bytes */ | |
#define RADA_V_TK1 7 /* track offset */ | |
#define RADA_M_TK1 0xFF | |
#define RADA_V_SC1 0 /* sector offset */ | |
#define RADA_M_SC1 0x7F | |
#define RADA_V_TK3 4 | |
#define RADA_M_TK3 0x3FF | |
#define RADA_V_SC3 0 | |
#define RADA_M_SC3 0xF | |
#define RADA_GETTK(x) (((x) >> rad_tab[rad_model].tk_v) & rad_tab[rad_model].tk_m) | |
#define RADA_GETSC(x) (((x) >> rad_tab[rad_model].sc_v) & rad_tab[rad_model].sc_m) | |
/* Address bad flag */ | |
#define RADA_INV 0x80 | |
/* Status byte 3 is current sector */ | |
/* Status byte 4 (7212 only) is failing sector */ | |
#define RADS_NBY1 4 /* num status bytes */ | |
#define RADS_NBY3 3 | |
/* Device state */ | |
#define RADS_INIT 0x101 | |
#define RADS_END 0x102 | |
#define RADS_WRITE 0x01 | |
#define RADS_READ 0x02 | |
#define RADS_SEEK 0x03 | |
#define RADS_SENSE 0x04 | |
#define RADS_CHECK 0x05 | |
#define RADS_RDEES 0x12 | |
/* Device status */ | |
#define RADV_OVR 0x80 /* overrun - NI */ | |
#define RADV_BADS 0x20 /* bad sector */ | |
#define RADV_WPE 0x10 | |
#define GET_PSC(x) ((int32) fmod (sim_gtime() / ((double) (x * RAD_WDSC)), \ | |
((double) rad_tab[rad_model].sctk))) | |
/* Model table */ | |
typedef struct { | |
uint32 tk_v; /* track extract */ | |
uint32 tk_m; | |
uint32 sc_v; /* sector extract */ | |
uint32 sc_m; | |
uint32 sctk; /* sectors/track */ | |
uint32 tkun; /* tracks/unit */ | |
uint32 nbys; /* bytes of status */ | |
} rad_t; | |
static rad_t rad_tab[] = { | |
{ RADA_V_TK1, RADA_M_TK1, RADA_V_SC1, RADA_M_SC1, RAD_SCTK1, RAD_TKUN1, RADS_NBY1 }, | |
{ RADA_V_TK3, RADA_M_TK3, RADA_V_SC3, RADA_M_SC3, RAD_SCTK3, RAD_TKUN3, RADS_NBY3 } | |
}; | |
uint32 rad_model = RAD_7212; /* model */ | |
uint32 rad_cmd = 0; /* state */ | |
uint32 rad_flags = 0; /* status flags */ | |
uint32 rad_ad = 0; /* rad address */ | |
uint32 rad_wlk = 0; /* write lock */ | |
uint32 rad_time = 2; /* inter-word time */ | |
extern uint32 chan_ctl_time; | |
uint32 rad_disp (uint32 op, uint32 dva, uint32 *dvst); | |
uint32 rad_tio_status (uint32 un); | |
uint32 rad_tdv_status (uint32 un); | |
t_stat rad_chan_err (uint32 st); | |
t_stat rad_svc (UNIT *uptr); | |
t_stat rad_reset (DEVICE *dptr); | |
t_stat rad_settype (UNIT *uptr, int32 val, CONST char *cptr, void *desc); | |
t_stat rad_showtype (FILE *st, UNIT *uptr, int32 val, CONST void *desc); | |
t_bool rad_inv_ad (uint32 *da); | |
t_bool rad_inc_ad (void); | |
t_bool rad_end_sec (UNIT *uptr, uint32 lnt, uint32 exp, uint32 st); | |
/* RAD data structures | |
rad_dev RAD device descriptor | |
rad_unit RAD unit descriptor | |
rad_reg RAD register list | |
*/ | |
dib_t rad_dib = { DVA_RAD, &rad_disp }; | |
UNIT rad_unit[] = { | |
{ UDATA (&rad_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+ | |
UNIT_MUSTBUF+UNIT_DISABLE, RAD_WDUNDF) }, | |
{ UDATA (&rad_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+ | |
UNIT_MUSTBUF+UNIT_DISABLE+UNIT_DIS, RAD_WDUNDF) }, | |
{ UDATA (&rad_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+ | |
UNIT_MUSTBUF+UNIT_DISABLE+UNIT_DIS, RAD_WDUNDF) }, | |
{ UDATA (&rad_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+ | |
UNIT_MUSTBUF+UNIT_DISABLE+UNIT_DIS, RAD_WDUNDF) } | |
}; | |
REG rad_reg[] = { | |
{ HRDATA (CMD, rad_cmd, 9) }, | |
{ HRDATA (FLAGS, rad_flags, 8) }, | |
{ HRDATA (ADDR, rad_ad, 15) }, | |
{ HRDATA (WLK, rad_wlk, RAD_N_WLK) }, | |
{ DRDATA (TIME, rad_time, 24), PV_LEFT }, | |
{ FLDATA (MODEL, rad_model, 0), REG_HRO }, | |
{ HRDATA (DEVNO, rad_dib.dva, 12), REG_HRO }, | |
{ NULL } | |
}; | |
MTAB rad_mod[] = { | |
{ MTAB_XTD | MTAB_VDV, RAD_7212, NULL, "7211", | |
&rad_settype, NULL, NULL }, | |
{ MTAB_XTD | MTAB_VDV, RAD_7212, NULL, "7212", | |
&rad_settype, NULL, NULL }, | |
{ MTAB_XTD | MTAB_VDV, RAD_7232, NULL, "7231", | |
&rad_settype, NULL, NULL }, | |
{ MTAB_XTD | MTAB_VDV, RAD_7232, NULL, "7232", | |
&rad_settype, NULL, NULL }, | |
{ MTAB_XTD | MTAB_VDV, 0, "TYPE", NULL, | |
NULL, &rad_showtype, 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 rad_dev = { | |
"RAD", rad_unit, rad_reg, rad_mod, | |
RAD_NUMDR, 16, 21, 1, 16, 32, | |
NULL, NULL, &rad_reset, | |
&io_boot, NULL, NULL, | |
&rad_dib, DEV_DISABLE | |
}; | |
/* RAD: IO dispatch routine */ | |
uint32 rad_disp (uint32 op, uint32 dva, uint32 *dvst) | |
{ | |
uint32 i; | |
uint32 un = DVA_GETUNIT (dva); | |
UNIT *uptr; | |
if ((un >= RAD_NUMDR) || /* inv unit num? */ | |
(rad_unit[un].flags & UNIT_DIS)) /* disabled unit? */ | |
return DVT_NODEV; | |
switch (op) { /* case on op */ | |
case OP_SIO: /* start I/O */ | |
*dvst = rad_tio_status (un); /* get status */ | |
if ((*dvst & (DVS_CST|DVS_DST)) == 0) { /* ctrl + dev idle? */ | |
rad_cmd = RADS_INIT; /* start dev thread */ | |
sim_activate (&rad_unit[un], chan_ctl_time); | |
} | |
break; | |
case OP_TIO: /* test status */ | |
*dvst = rad_tio_status (un); /* return status */ | |
break; | |
case OP_TDV: /* test status */ | |
*dvst = rad_tdv_status (un); /* return status */ | |
break; | |
case OP_HIO: /* halt I/O */ | |
chan_clr_chi (rad_dib.dva); /* clr int*/ | |
*dvst = rad_tio_status (un); /* get status */ | |
if ((*dvst & DVS_CST) != 0) { /* ctrl busy? */ | |
for (i = 0; i < RAD_NUMDR; i++) { /* find busy unit */ | |
uptr = &rad_unit[i]; | |
if (sim_is_active (uptr)) { /* active? */ | |
sim_cancel (uptr); /* stop */ | |
chan_uen (rad_dib.dva); /* uend */ | |
} /* end if active */ | |
} /* end for */ | |
} | |
break; | |
case OP_AIO: /* acknowledge int */ | |
chan_clr_chi (rad_dib.dva); /* clr int */ | |
*dvst = rad_tdv_status (0); /* status like TDV */ | |
break; | |
default: | |
*dvst = 0; | |
return SCPE_IERR; | |
} | |
return 0; | |
} | |
/* Unit service - this code assumes the entire disk is buffered */ | |
t_stat rad_svc (UNIT *uptr) | |
{ | |
uint32 i, sc, da, cmd, wd, wd1, c[4], gp; | |
uint32 *fbuf = (uint32 *) uptr->filebuf; | |
uint32 st; | |
int32 t; | |
switch (rad_cmd) { | |
case RADS_INIT: /* init state */ | |
st = chan_get_cmd (rad_dib.dva, &cmd); /* get command */ | |
if (CHS_IFERR (st)) /* channel error? */ | |
return rad_chan_err (st); | |
if ((cmd == 0) || /* invalid cmd? */ | |
((cmd > RADS_CHECK) && (cmd != RADS_RDEES))) { | |
chan_uen (rad_dib.dva); /* uend */ | |
return SCPE_OK; | |
} | |
rad_flags = 0; /* clear status */ | |
rad_cmd = cmd & 0x7; /* next state */ | |
if ((cmd == RADS_SEEK) || (cmd == RADS_SENSE)) /* seek or sense? */ | |
sim_activate (uptr, chan_ctl_time); /* schedule soon */ | |
else { /* data transfer */ | |
sc = RADA_GETSC (rad_ad); /* new sector */ | |
t = sc - GET_PSC (rad_time); /* delta to new */ | |
if (t < 0) /* wrap around? */ | |
t = t + rad_tab[rad_model].sctk; | |
sim_activate (uptr, t * rad_time * RAD_WDSC); /* schedule op */ | |
} | |
return SCPE_OK; | |
case RADS_END: /* end state */ | |
st = chan_end (rad_dib.dva); /* set channel end */ | |
if (CHS_IFERR (st)) /* channel error? */ | |
return rad_chan_err (st); | |
if (st == CHS_CCH) { /* command chain? */ | |
rad_cmd = RADS_INIT; /* restart thread */ | |
sim_activate (uptr, chan_ctl_time); | |
} | |
return SCPE_OK; /* done */ | |
case RADS_SEEK: /* seek */ | |
c[0] = c[1] = 0; | |
for (i = 0, st = 0; (i < 2) && (st != CHS_ZBC); i++) { | |
st = chan_RdMemB (rad_dib.dva, &c[i]); /* get byte */ | |
if (CHS_IFERR (st)) /* channel error? */ | |
return rad_chan_err (st); | |
} | |
rad_ad = ((c[0] & 0x7F) << 8) | c[1]; /* new address */ | |
if (((i != 2) || (st != CHS_ZBC)) && /* length error? */ | |
chan_set_chf (rad_dib.dva, CHF_LNTE)) /* care? */ | |
return SCPE_OK; | |
break; | |
case RADS_SENSE: /* sense */ | |
c[0] = ((rad_ad >> 8) & 0x7F) | (rad_inv_ad (NULL)? RADA_INV: 0); | |
c[1] = rad_ad & 0xFF; /* address */ | |
c[2] = GET_PSC (rad_time); /* curr sector */ | |
c[3] = 0; | |
for (i = 0, st = 0; (i < rad_tab[rad_model].nbys) && (st != CHS_ZBC); i++) { | |
st = chan_WrMemB (rad_dib.dva, c[i]); /* store char */ | |
if (CHS_IFERR (st)) /* channel error? */ | |
return rad_chan_err (st); | |
} | |
if (((i != rad_tab[rad_model].nbys) || (st != CHS_ZBC)) && | |
chan_set_chf (rad_dib.dva, CHF_LNTE)) /* length error? */ | |
return SCPE_OK; | |
break; | |
case RADS_WRITE: /* write */ | |
gp = (RADA_GETSC (rad_ad) * RAD_N_WLK) / /* write lock group */ | |
rad_tab[rad_model].tkun; | |
if ((rad_wlk >> gp) & 1) { /* write lock set? */ | |
rad_flags |= RADV_WPE; /* set status */ | |
chan_uen (rad_dib.dva); /* uend */ | |
return SCPE_OK; | |
} /* fall through */ | |
if (rad_inv_ad (&da)) { /* invalid addr? */ | |
chan_uen (rad_dib.dva); /* uend */ | |
return SCPE_OK; | |
} | |
for (i = 0, st = 0; i < RAD_WDSC; da++, i++) { /* write */ | |
if (st != CHS_ZBC) { /* chan active? */ | |
st = chan_RdMemW (rad_dib.dva, &wd); /* get data */ | |
if (CHS_IFERR (st)) { /* channel error? */ | |
rad_inc_ad (); /* da increments */ | |
return rad_chan_err (st); | |
} | |
} | |
else wd = 0; | |
fbuf[da] = wd; /* store in buffer */ | |
if (da >= uptr->hwmark) /* update length */ | |
uptr->hwmark = da + 1; | |
} | |
if (rad_end_sec (uptr, i, RAD_WDSC, st)) /* transfer done? */ | |
return SCPE_OK; | |
break; | |
/* Must be done by bytes to get precise miscompare */ | |
case RADS_CHECK: /* write check */ | |
if (rad_inv_ad (&da)) { /* invalid addr? */ | |
chan_uen (rad_dib.dva); /* uend */ | |
return SCPE_OK; | |
} | |
for (i = 0, st = 0; (i < (RAD_WDSC * 4)) && (st != CHS_ZBC); ) { | |
st = chan_RdMemB (rad_dib.dva, &wd); /* read sector */ | |
if (CHS_IFERR (st)) { /* channel error? */ | |
rad_inc_ad (); /* da increments */ | |
return rad_chan_err (st); | |
} | |
wd1 = (fbuf[da] >> (24 - ((i % 4) * 8))) & 0xFF; /* byte */ | |
if (wd != wd1) { /* check error? */ | |
rad_inc_ad (); /* da increments */ | |
chan_set_chf (rad_dib.dva, CHF_XMDE); /* set xmt err flag */ | |
chan_uen (rad_dib.dva); /* force uend */ | |
return SCPE_OK; | |
} | |
da = da + ((++i % 4) == 0); /* every 4th byte */ | |
} | |
if (rad_end_sec (uptr, i, RAD_WDSC * 4, st)) /* transfer done? */ | |
return SCPE_OK; | |
break; | |
case RADS_READ: /* read */ | |
if (rad_inv_ad (&da)) { /* invalid addr? */ | |
chan_uen (rad_dib.dva); /* uend */ | |
return SCPE_OK; | |
} | |
for (i = 0, st = 0; (i < RAD_WDSC) && (st != CHS_ZBC); da++, i++) { | |
st = chan_WrMemW (rad_dib.dva, fbuf[da]); /* store in mem */ | |
if (CHS_IFERR (st)) { /* channel error? */ | |
rad_inc_ad (); /* da increments */ | |
return rad_chan_err (st); | |
} | |
} | |
if (rad_end_sec (uptr, i, RAD_WDSC, st)) /* transfer done? */ | |
return SCPE_OK; | |
break; | |
} | |
rad_cmd = RADS_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 rad_end_sec (UNIT *uptr, uint32 lnt, uint32 exp, uint32 st) | |
{ | |
if (st != CHS_ZBC) { /* end record? */ | |
if (rad_inc_ad ()) /* inc addr, ovf? */ | |
chan_uen (rad_dib.dva); /* uend */ | |
else sim_activate (uptr, rad_time * 16); /* no, next sector */ | |
return TRUE; | |
} | |
rad_inc_ad (); /* just incr addr */ | |
if ((lnt != exp) && /* length error? */ | |
chan_set_chf (rad_dib.dva, CHF_LNTE)) /* do we care? */ | |
return TRUE; | |
return FALSE; /* cmd done */ | |
} | |
/* RAD status routine */ | |
uint32 rad_tio_status (uint32 un) | |
{ | |
uint32 i, st; | |
st = DVS_AUTO; /* flags */ | |
if (sim_is_active (&rad_unit[un])) /* active => busy */ | |
st |= DVS_DBUSY; | |
else if ((rad_unit[un].flags & UNIT_ATT) == 0) /* not att => offl */ | |
st |= DVS_DOFFL; | |
for (i = 0; i < RAD_NUMDR; i++) { /* loop thru units */ | |
if (sim_is_active (&rad_unit[i])) { /* active? */ | |
st |= (DVS_CBUSY |(CC2 << DVT_V_CC)); /* ctrl is busy */ | |
return st; | |
} | |
} | |
return st; | |
} | |
uint32 rad_tdv_status (uint32 un) | |
{ | |
uint32 st; | |
st = rad_flags; | |
if (rad_inv_ad (NULL)) /* bad address? */ | |
st |= RADV_BADS; | |
return st; | |
} | |
/* Validate disk address */ | |
t_bool rad_inv_ad (uint32 *da) | |
{ | |
uint32 tk = RADA_GETTK (rad_ad); | |
uint32 sc = RADA_GETSC (rad_ad); | |
if ((tk >= rad_tab[rad_model].tkun) || /* bad sec or trk? */ | |
(sc >= rad_tab[rad_model].sctk)) { | |
return TRUE; | |
} | |
if (da) /* return word addr */ | |
*da = ((tk * rad_tab[rad_model].sctk) + sc) * RAD_WDSC; | |
return FALSE; | |
} | |
/* Increment disk address */ | |
t_bool rad_inc_ad (void) | |
{ | |
uint32 tk = RADA_GETTK (rad_ad); | |
uint32 sc = RADA_GETSC (rad_ad); | |
sc = sc + 1; /* sector++ */ | |
if (sc >= rad_tab[rad_model].sctk) { /* overflow? */ | |
sc = 0; /* wrap sector */ | |
tk = tk + 1; /* track++ */ | |
} | |
rad_ad = ((tk << rad_tab[rad_model].tk_v) | /* rebuild rad_ad */ | |
(sc << rad_tab[rad_model].sc_v)); | |
if (tk >= rad_tab[rad_model].tkun) /* overflow? */ | |
return TRUE; | |
return FALSE; | |
} | |
/* Channel error */ | |
t_stat rad_chan_err (uint32 st) | |
{ | |
chan_uen (rad_dib.dva); /* uend */ | |
if (st < CHS_ERR) | |
return st; | |
return SCPE_OK; | |
} | |
/* Reset routine */ | |
t_stat rad_reset (DEVICE *dptr) | |
{ | |
uint32 i; | |
for (i = 0; i < RAD_NUMDR; i++) | |
sim_cancel (&rad_unit[i]); /* stop dev thread */ | |
rad_cmd = 0; | |
rad_flags = 0; | |
rad_ad = 0; | |
chan_reset_dev (rad_dib.dva); /* clr int, active */ | |
return SCPE_OK; | |
} | |
/* Set controller type */ | |
t_stat rad_settype (UNIT *uptr, int32 val, CONST char *cptr, void *desc) | |
{ | |
uint32 i; | |
for (i = 0; i < RAD_NUMDR; i++) { /* all units unatt? */ | |
if (rad_unit[i].flags & UNIT_ATT) | |
return SCPE_ALATT; | |
} | |
rad_model = val; /* update model */ | |
rad_reset (&rad_dev); /* reset */ | |
for (i = 0; i < RAD_NUMDR; i++) /* update capacity */ | |
rad_unit[i].capac = RAD_WDUN; | |
return SCPE_OK; | |
} | |
/* Show controller type */ | |
t_stat rad_showtype (FILE *st, UNIT *uptr, int32 val, CONST void *desc) | |
{ | |
fprintf (st, (rad_model == RAD_7212)? "7211/7212": "7231/7232"); | |
return SCPE_OK; | |
} |