/* i1620_dp.c: IBM 1311 disk simulator | |
Copyright (c) 2002-2017, 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. | |
dp 1311 disk pack | |
The 1311 disk pack has 100 cylinders, 10 tracks/cylinder, 20 sectors/track. | |
Each sector contains 105 characters of information: | |
5c sector address | |
100c sector data | |
By default, a sector's address field will be '00000', which is interpreted | |
to mean the implied sector number that would be in place if the disk pack | |
had been formatted with sequential sector numbers. | |
13-Mar-17 RMS Fixed bug in write check function test (COVERITY) | |
18-Oct-02 RMS Fixed bug in error testing (Hans Pufal) | |
*/ | |
#include "i1620_defs.h" | |
#define DP_NUMDR 4 /* #drives */ | |
#define UNIT_V_WAE (UNIT_V_UF + 0) /* write addr enab */ | |
#define UNIT_WAE (1 << UNIT_V_WAE) | |
/* Disk format */ | |
#define DP_ADDR 5 /* address */ | |
#define DP_DATA 100 /* data */ | |
#define DP_NUMCH (DP_ADDR + DP_DATA) | |
#define DP_NUMSC 20 /* #sectors */ | |
#define DP_NUMSF 10 /* #surfaces */ | |
#define DP_NUMCY 100 /* #cylinders */ | |
#define DP_TOTSC (DP_NUMCY * DP_NUMSF * DP_NUMSC) | |
#define DP_SIZE (DP_TOTSC * DP_NUMCH) | |
/* Disk control field */ | |
#define DCF_DRV 0 /* drive select */ | |
#define DCF_SEC 1 /* sector addr */ | |
#define DCF_SEC_LEN 5 | |
#define DCF_CNT (DCF_SEC + DCF_SEC_LEN) /* sector count */ | |
#define DCF_CNT_LEN 3 | |
#define DCF_ADR (DCF_CNT + DCF_CNT_LEN) /* buffer address */ | |
#define DCF_ADR_LEN 5 | |
#define DCF_LEN (DCF_ADR + DCF_ADR_LEN) | |
/* Functions */ | |
#define FNC_SEEK 1 /* seek */ | |
#define FNC_SEC 0 /* sectors */ | |
#define FNC_WCH 1 /* write check */ | |
#define FNC_NRL 2 /* no rec lnt chk */ | |
#define FNC_TRK 4 /* tracks */ | |
#define FNC_WRI 8 /* write offset */ | |
#define CYL u3 /* current cylinder */ | |
extern uint8 M[MAXMEMSIZE]; /* memory */ | |
extern uint8 ind[NUM_IND]; | |
extern UNIT cpu_unit; | |
int32 dp_stop = 1; /* disk err stop */ | |
uint32 dp_ba = 0; /* buffer addr */ | |
t_stat dp_reset (DEVICE *dptr); | |
t_stat dp_rdadr (UNIT *uptr, int32 sec, int32 qnr, int32 qwc); | |
t_stat dp_rdsec (UNIT *uptr, int32 sec, int32 qnr, int32 qwc); | |
t_stat dp_wradr (UNIT *uptr, int32 sec, int32 qnr); | |
t_stat dp_wrsec (UNIT *uptr, int32 sec, int32 qnr); | |
int32 dp_fndsec (UNIT *uptr, int32 sec, t_bool rd); | |
t_stat dp_nexsec (UNIT *uptr, int32 sec, int32 psec, t_bool rd); | |
t_bool dp_zeroad (uint8 *ap); | |
int32 dp_cvt_ad (uint8 *ap); | |
int32 dp_trkop (int32 drv, int32 sec); | |
int32 dp_cvt_bcd (uint32 ad, int32 len); | |
void dp_fill (UNIT *uptr, uint32 da, int32 cnt); | |
t_stat dp_tstgm (uint32 c, int32 qnr); | |
/* DP data structures | |
dp_dev DP device descriptor | |
dp_unit DP unit list | |
dp_reg DP register list | |
dp_mod DP modifier list | |
*/ | |
UNIT dp_unit[] = { | |
{ UDATA (NULL, UNIT_FIX + UNIT_DISABLE + UNIT_ATTABLE + | |
UNIT_BUFABLE + UNIT_MUSTBUF + UNIT_BCD, DP_SIZE) }, | |
{ UDATA (NULL, UNIT_FIX + UNIT_DISABLE + UNIT_ATTABLE + | |
UNIT_BUFABLE + UNIT_MUSTBUF + UNIT_BCD, DP_SIZE) }, | |
{ UDATA (NULL, UNIT_FIX + UNIT_DISABLE + UNIT_ATTABLE + | |
UNIT_BUFABLE + UNIT_MUSTBUF + UNIT_BCD, DP_SIZE) }, | |
{ UDATA (NULL, UNIT_FIX + UNIT_DISABLE + UNIT_ATTABLE + | |
UNIT_BUFABLE + UNIT_MUSTBUF + UNIT_BCD, DP_SIZE) } | |
}; | |
REG dp_reg[] = { | |
{ FLDATAD (ADCHK, ind[IN_DACH], 0, "address check (compare error) indicator") }, | |
{ FLDATAD (WLRC, ind[IN_DWLR], 0, "wrong length record check indicator") }, | |
{ FLDATAD (CYLO, ind[IN_DCYO], 0, "cylinder overflow check indicator") }, | |
{ FLDATAD (ERR, ind[IN_DERR], 0, "disk error indicator") }, | |
{ FLDATAD (DPSTOP, dp_stop, 0, "disk check stop") }, | |
{ URDATAD (CYL, dp_unit[0].CYL, 10, 8, 0, | |
DP_NUMDR, PV_LEFT + REG_RO, "Cylinder") }, | |
{ NULL } | |
}; | |
MTAB dp_mod[] = { | |
{ UNIT_WAE, 0, "write address disabled", "ADDROFF", NULL, NULL, NULL, "set unit n address enable off" }, | |
{ UNIT_WAE, UNIT_WAE, "write address enabled", "ADDRON", NULL, NULL, NULL, "set unit n address enable on" }, | |
{ 0 } | |
}; | |
DEVICE dp_dev = { | |
"DP", dp_unit, dp_reg, dp_mod, | |
DP_NUMDR, 10, 21, 1, 16, 5, | |
NULL, NULL, &dp_reset, | |
NULL, NULL, NULL | |
}; | |
/* Disk IO routine */ | |
t_stat dp (uint32 op, uint32 pa, uint32 f0, uint32 f1) | |
{ | |
int32 drv, sa, sec, psec, cnt, qwc, qnr, t; | |
UNIT *uptr; | |
t_stat r; | |
if (pa & 1) /* dcf must be even */ | |
return STOP_INVDCF; | |
ind[IN_DACH] = ind[IN_DWLR] = 0; /* clr indicators */ | |
ind[IN_DERR] = ind[IN_DCYO] = 0; | |
sa = ADDR_A (pa, DCF_SEC); /* ptr to sector */ | |
if (((dp_unit[0].flags & UNIT_DIS) == 0) && /* only drive 0? */ | |
(dp_unit[1].flags & UNIT_DIS) && | |
(dp_unit[2].flags & UNIT_DIS) && | |
(dp_unit[3].flags & UNIT_DIS)) drv = 0; /* ignore drv select */ | |
else drv = (((M[pa] & 1)? M[pa]: M[sa]) & 0xE) >> 1; /* drive # */ | |
if (drv >= DP_NUMDR) /* invalid? */ | |
return STOP_INVDRV; | |
uptr = dp_dev.units + drv; /* get unit ptr */ | |
if ((uptr->flags & UNIT_ATT) == 0) { /* attached? */ | |
ind[IN_DERR] = 1; /* no, error */ | |
CRETIOE (dp_stop, SCPE_UNATT); | |
} | |
sec = dp_cvt_bcd (sa, DCF_SEC_LEN); /* cvt sector */ | |
if ((sec < 0) || (sec >= (DP_NUMDR * DP_TOTSC))) /* bad sector? */ | |
return STOP_INVDSC; | |
if (op == OP_K) { /* seek? */ | |
if (f1 != FNC_SEEK) /* really? */ | |
return STOP_INVFNC; | |
uptr->CYL = (sec / (DP_NUMSF * DP_NUMSC)) % /* set cyl # */ | |
DP_NUMCY; | |
return SCPE_OK; /* done! */ | |
} | |
cnt = dp_cvt_bcd (ADDR_A (pa, DCF_CNT), DCF_CNT_LEN); /* get count */ | |
t = dp_cvt_bcd (ADDR_A (pa, DCF_ADR), DCF_ADR_LEN); /* get address */ | |
if ((t < 0) || (t & 1)) /* bad address? */ | |
return STOP_INVDBA; | |
dp_ba = t; /* save addr */ | |
if (f1 >= FNC_WRI) /* invalid func? */ | |
return STOP_INVFNC; | |
if (op == OP_RN) /* read? set wch */ | |
qwc = f1 & FNC_WCH; | |
else if (op == OP_WN) { /* write? */ | |
if (f1 & FNC_WCH) /* cant check */ | |
return STOP_INVFNC; | |
f1 = f1 + FNC_WRI; /* offset fnc */ | |
} | |
else return STOP_INVFNC; /* not R or W */ | |
qnr = f1 & FNC_NRL; /* no rec check? */ | |
switch (f1 & ~(FNC_WCH | FNC_NRL)) { /* case on function */ | |
case FNC_SEC: /* read sectors */ | |
if (cnt <= 0) /* bad count? */ | |
return STOP_INVDCN; | |
psec = dp_fndsec (uptr, sec, TRUE); /* find sector */ | |
if (psec < 0) /* error? */ | |
CRETIOE (dp_stop, STOP_DACERR); | |
do { /* loop on count */ | |
if ((r = dp_rdsec (uptr, psec, qnr, qwc))) /* read sector */ | |
break; | |
sec++; psec++; /* next sector */ | |
} while ((--cnt > 0) && | |
((r = dp_nexsec (uptr, sec, psec, TRUE)) == SCPE_OK)); | |
break; /* done, clean up */ | |
case FNC_TRK: /* read track */ | |
psec = dp_trkop (drv, sec); /* start of track */ | |
for (cnt = 0; cnt < DP_NUMSC; cnt++) { /* full track */ | |
if ((r = dp_rdadr (uptr, psec, qnr, qwc))) /* read addr */ | |
break; /* error? */ | |
if ((r = dp_rdsec (uptr, psec, qnr, qwc))) /* read data */ | |
break; /* error? */ | |
psec = dp_trkop (drv, sec) + ((psec + 1) % DP_NUMSC); | |
} | |
break; /* done, clean up */ | |
case FNC_SEC + FNC_WRI: /* write sectors */ | |
if (cnt <= 0) /* bad count? */ | |
return STOP_INVDCN; | |
psec = dp_fndsec (uptr, sec, FALSE); /* find sector */ | |
if (psec < 0) /* error? */ | |
CRETIOE (dp_stop, STOP_DACERR); | |
do { /* loop on count */ | |
if ((r = dp_tstgm (M[dp_ba], qnr))) /* start with gm? */ | |
break; | |
if ((r = dp_wrsec (uptr, psec, qnr))) /* write data */ | |
break; | |
sec++; psec++; /* next sector */ | |
} while ((--cnt > 0) && | |
((r = dp_nexsec (uptr, sec, psec, FALSE)) == SCPE_OK)); | |
break; /* done, clean up */ | |
case FNC_TRK + FNC_WRI: /* write track */ | |
if ((uptr->flags & UNIT_WAE) == 0) /* enabled? */ | |
return STOP_WRADIS; | |
psec = dp_trkop (drv, sec); /* start of track */ | |
for (cnt = 0; cnt < DP_NUMSC; cnt++) { /* full track */ | |
if ((r = dp_tstgm (M[dp_ba], qnr))) /* start with gm? */ | |
break; | |
if ((r = dp_wradr (uptr, psec, qnr))) /* write addr */ | |
break; | |
if ((r = dp_wrsec (uptr, psec, qnr))) /* write data */ | |
break; | |
psec = dp_trkop (drv, sec) + ((psec + 1) % DP_NUMSC); | |
} | |
break; /* done, clean up */ | |
default: /* unknown */ | |
return STOP_INVFNC; | |
} | |
if ((r == SCPE_OK) && !qnr) { /* eor check? */ | |
if ((M[dp_ba] & DIGIT) != GRP_MARK) { /* GM at end? */ | |
ind[IN_DWLR] = ind[IN_DERR] = 1; /* no, error */ | |
r = STOP_WRLERR; | |
} | |
} | |
if ((r != SCPE_OK) && /* error? */ | |
(dp_stop || !ind[IN_DERR])) /* iochk or stop? */ | |
return r; | |
return SCPE_OK; /* continue */ | |
} | |
/* Read or compare address with memory */ | |
t_stat dp_rdadr (UNIT *uptr, int32 sec, int32 qnr, int32 qwc) | |
{ | |
int32 i; | |
uint8 ad; | |
int32 da = (sec % DP_TOTSC) * DP_NUMCH; /* char number */ | |
uint8 *ap = ((uint8 *) uptr->filebuf) + da; /* buf ptr */ | |
t_bool zad = dp_zeroad (ap); /* zero address */ | |
static const int32 dec_tab[DP_ADDR] = { /* powers of 10 */ | |
10000, 1000, 100, 10, 1 | |
} ; | |
for (i = 0; i < DP_ADDR; i++) { /* copy/check addr */ | |
if (zad) { /* addr zero? */ | |
ad = sec / dec_tab[i]; /* get addr digit */ | |
sec = sec % dec_tab[i]; /* get remainder */ | |
} | |
else ad = *ap; /* addr digit */ | |
if (qwc) { /* write check? */ | |
if (dp_tstgm (M[dp_ba], qnr)) /* grp mrk in mem? */ | |
return STOP_WRLERR; /* yes, error */ | |
if (!zad && (M[dp_ba] != ad)) { /* digits equal? */ | |
ind[IN_DACH] = ind[IN_DERR] = 1; /* no, error */ | |
return STOP_DWCERR; | |
} | |
} | |
else M[dp_ba] = ad & (FLAG | DIGIT); /* store digit */ | |
if (dp_tstgm (*ap, qnr)) /* grp mrk on disk? */ | |
return STOP_WRLERR; | |
ap++; PP (dp_ba); /* adv ptrs */ | |
} | |
return SCPE_OK; | |
} | |
/* Read or compare data with memory */ | |
t_stat dp_rdsec (UNIT *uptr, int32 sec, int32 qnr, int32 qwc) | |
{ | |
int32 i; | |
int32 da = (sec % DP_TOTSC) * DP_NUMCH; /* char number */ | |
uint8 *ap = ((uint8 *) uptr->filebuf) + da + DP_ADDR; /* buf ptr */ | |
for (i = 0; i < DP_DATA; i++) { /* copy data */ | |
if (qwc) { /* write check? */ | |
if (dp_tstgm (M[dp_ba], qnr)) /* grp mrk in mem? */ | |
return STOP_WRLERR; /* yes, error */ | |
if (M[dp_ba] != *ap) { /* dig+flags equal? */ | |
ind[IN_DACH] = ind[IN_DERR] = 1; /* no, error */ | |
return STOP_DWCERR; | |
} | |
} | |
else M[dp_ba] = *ap & (FLAG | DIGIT); /* flag + digit */ | |
if (dp_tstgm (*ap, qnr)) /* grp mrk on disk? */ | |
return STOP_WRLERR; | |
ap++; PP (dp_ba); /* adv ptrs */ | |
} | |
return SCPE_OK; | |
} | |
/* Write address to disk */ | |
t_stat dp_wradr (UNIT *uptr, int32 sec, int32 qnr) | |
{ | |
int32 i; | |
uint32 da = (sec % DP_TOTSC) * DP_NUMCH; /* char number */ | |
uint8 *ap = ((uint8 *) uptr->filebuf) + da; /* buf ptr */ | |
for (i = 0; i < DP_ADDR; i++) { /* copy address */ | |
*ap = M[dp_ba] & (FLAG | DIGIT); /* flag + digit */ | |
if (da >= uptr->hwmark) | |
uptr->hwmark = da + 1; | |
if (dp_tstgm (*ap, qnr)) { /* grp mrk fm mem? */ | |
dp_fill (uptr, da + 1, DP_NUMCH - i - 1); /* fill addr+data */ | |
return STOP_WRLERR; /* error */ | |
} | |
da++; ap++; PP (dp_ba); /* adv ptrs */ | |
} | |
return SCPE_OK; | |
} | |
/* Write data to disk */ | |
t_stat dp_wrsec (UNIT *uptr, int32 sec, int32 qnr) | |
{ | |
int32 i; | |
uint32 da = ((sec % DP_TOTSC) * DP_NUMCH) + DP_ADDR; /* char number */ | |
uint8 *ap = ((uint8 *) uptr->filebuf) + da; /* buf ptr */ | |
for (i = 0; i < DP_DATA; i++) { /* copy data */ | |
*ap = M[dp_ba] & (FLAG | DIGIT); /* get character */ | |
if (da >= uptr->hwmark) | |
uptr->hwmark = da + 1; | |
if (dp_tstgm (*ap, qnr)) { /* grp mrk fm mem? */ | |
dp_fill (uptr, da + 1, DP_DATA - i - 1); /* fill data */ | |
return STOP_WRLERR; /* error */ | |
} | |
da++; ap++; PP (dp_ba); /* adv ptrs */ | |
} | |
return SCPE_OK; | |
} | |
/* Find sector */ | |
int32 dp_fndsec (UNIT *uptr, int32 sec, t_bool rd) | |
{ | |
int32 ctrk = sec % (DP_NUMSF * DP_NUMSC); /* curr trk-sec */ | |
int32 psec = ((uptr->CYL) * (DP_NUMSF * DP_NUMSC)) + ctrk; | |
int32 da = psec * DP_NUMCH; /* char number */ | |
uint8 *ap = ((uint8 *) uptr->filebuf) + da; /* buf ptr */ | |
int32 dskad, i; | |
if (dp_zeroad (ap)) /* addr zero? ok */ | |
return psec; | |
dskad = dp_cvt_ad (ap); /* cvt addr */ | |
if (dskad == sec) { /* match? */ | |
if (rd || ((*ap & FLAG) == 0)) /* read or !wprot? */ | |
return psec; | |
ind[IN_DACH] = ind[IN_DERR] = 1; /* no match */ | |
return -1; | |
} | |
psec = psec - (psec % DP_NUMSC); /* sector 0 */ | |
for (i = 0; i < DP_NUMSC; i++, psec++) { /* check track */ | |
da = psec * DP_NUMCH; /* char number */ | |
ap = ((uint8 *) uptr->filebuf) + da; /* word pointer */ | |
if (dp_zeroad (ap)) /* no implicit match */ | |
continue; | |
dskad = dp_cvt_ad (ap); /* cvt addr */ | |
if (dskad == sec) { /* match? */ | |
if (rd || ((*ap & FLAG) == 0)) /* read or !wprot? */ | |
return psec; | |
ind[IN_DACH] = ind[IN_DERR] = 1; /* no match */ | |
return -1; | |
} | |
} | |
ind[IN_DACH] = ind[IN_DERR] = 1; /* no match */ | |
return -1; | |
} | |
/* Find next sector - must be sequential, cannot cross cylinder boundary */ | |
t_stat dp_nexsec (UNIT *uptr, int32 sec, int32 psec, t_bool rd) | |
{ | |
int32 ctrk = psec % (DP_NUMSF * DP_NUMSC); /* curr trk-sec */ | |
int32 da = psec * DP_NUMCH; /* word number */ | |
uint8 *ap = ((uint8 *) uptr->filebuf) + da; /* buf ptr */ | |
int32 dskad; | |
if (ctrk) { /* not trk zero? */ | |
if (dp_zeroad (ap)) /* addr zero? ok */ | |
return SCPE_OK; | |
dskad = dp_cvt_ad (ap); /* cvt addr */ | |
if ((dskad == sec) && /* match? */ | |
(rd || ((*ap & FLAG) == 0))) /* read or !wprot? */ | |
return SCPE_OK; | |
ind[IN_DACH] = ind[IN_DERR] = 1; /* no, error */ | |
return STOP_DACERR; | |
} | |
ind[IN_DCYO] = ind[IN_DERR] = 1; /* cyl overflow */ | |
return STOP_CYOERR; | |
} | |
/* Test for zero address */ | |
t_bool dp_zeroad (uint8 *ap) | |
{ | |
int32 i; | |
for (i = 0; i < DP_ADDR; i++, ap++) { /* loop thru addr */ | |
if (*ap & DIGIT) /* nonzero? lose */ | |
return FALSE; | |
} | |
return TRUE; /* all zeroes */ | |
} | |
/* Test for group mark when enabled */ | |
t_stat dp_tstgm (uint32 c, int32 qnr) | |
{ | |
if (!qnr && ((c & DIGIT) == GRP_MARK)) { /* premature GM? */ | |
ind[IN_DWLR] = ind[IN_DERR] = 1; /* error */ | |
return STOP_WRLERR; | |
} | |
return SCPE_OK; | |
} | |
/* Convert disk address to binary - invalid char force bad address */ | |
int32 dp_cvt_ad (uint8 *ap) | |
{ | |
int32 i, r; | |
uint8 c; | |
for (i = r = 0; i < DP_ADDR; i++, ap++) { /* loop thru addr */ | |
c = *ap & DIGIT; /* get digit */ | |
if (BAD_DIGIT (c)) /* bad digit? */ | |
return -1; | |
r = (r * 10) + c; /* bcd to binary */ | |
} | |
return r; | |
} | |
/* Track operation setup */ | |
int32 dp_trkop (int32 drv, int32 sec) | |
{ | |
int32 ctrk = (sec / DP_NUMSC) % DP_NUMSF; | |
return ((drv * DP_TOTSC) + (dp_unit[drv].CYL * DP_NUMSF * DP_NUMSC) + | |
(ctrk * DP_NUMSC)); | |
} | |
/* Convert DCF BCD field to binary */ | |
int32 dp_cvt_bcd (uint32 ad, int32 len) | |
{ | |
uint8 c; | |
int32 r; | |
for (r = 0; len > 0; len--) { /* loop thru char */ | |
c = M[ad] & DIGIT; /* get digit */ | |
if (BAD_DIGIT (c)) /* invalid? */ | |
return -1; | |
r = (r * 10) + c; /* cvt to bin */ | |
PP (ad); /* next digit */ | |
} | |
return r; | |
} | |
/* Fill sector buffer with zero */ | |
void dp_fill (UNIT *uptr, uint32 da, int32 cnt) | |
{ | |
while (cnt-- > 0) { /* fill with zeroes*/ | |
*(((uint8 *) uptr->filebuf) + da) = 0; | |
if (da >= uptr->hwmark) | |
uptr->hwmark = da + 1; | |
da++; | |
} | |
return; | |
} | |
/* Reset routine */ | |
t_stat dp_reset (DEVICE *dptr) | |
{ | |
int32 i; | |
for (i = 0; i < DP_NUMDR; i++) /* reset cylinder */ | |
dp_unit[i].CYL = 0; | |
ind[IN_DACH] = ind[IN_DWLR] = 0; /* clr indicators */ | |
ind[IN_DERR] = ind[IN_DCYO] = 0; | |
return SCPE_OK; | |
} |