/* ibm1130_disk.c: IBM 1130 disk IO simulator | |
NOTE - there is a problem with this code. The Device Status Word (DSW) is | |
computed from current conditions when requested by an XIO load status | |
command; the value of DSW available to the simulator's examine & save | |
commands may NOT be accurate. This should probably be fixed. | |
Copyright (c) 2002, Brian Knittel | |
Based on PDP-11 simulator written by Robert M Supnik | |
Revision History | |
31July2001 - Derived from pdp11_stddev.c, which carries this disclaimer: | |
Copyright (c) 1993-2001, 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. | |
*/ | |
#include "ibm1130_defs.h" | |
/* Constants */ | |
#define DSK_NUMWD 321 /* words/sector */ | |
#define DSK_NUMSC 4 /* sectors/surface */ | |
#define DSK_NUMSF 2 /* surfaces/cylinder */ | |
#define DSK_NUMCY 203 /* cylinders/drive */ | |
#define DSK_NUMTR (DSK_NUMCY * DSK_NUMSF) /* tracks/drive */ | |
#define DSK_NUMDR 5 /* drives/controller */ | |
#define DSK_SIZE (DSK_NUMCY * DSK_NUMSF * DSK_NUMSC * DSK_NUMWD) /* words/drive */ | |
#define UNIT_V_RONLY (UNIT_V_UF + 0) /* hwre write lock */ | |
#define UNIT_V_OPERR (UNIT_V_UF + 1) /* operation error flag */ | |
#define UNIT_V_HARDERR (UNIT_V_UF + 2) /* hard error flag (reset on power down) */ | |
#define UNIT_RONLY (1u << UNIT_V_RONLY) | |
#define UNIT_OPERR (1u << UNIT_V_OPERR) | |
#define UNIT_HARDERR (1u << UNIT_V_HARDERR) | |
static int16 dsk_dsw[DSK_NUMDR] = {0}; /* device status words */ | |
static int16 dsk_sec[DSK_NUMDR] = {0}; /* next-sector-up */ | |
int32 dsk_swait = 10; /* seek time */ | |
int32 dsk_rwait = 10; /* rotate time */ | |
#define DSK_DSW_DATA_ERROR 0x8000 /* device status word bits */ | |
#define DSK_DSW_OP_COMPLETE 0x4000 | |
#define DSK_DSW_NOT_READY 0x2000 | |
#define DSK_DSW_DISK_BUSY 0x1000 | |
#define DSK_DSW_CARRIAGE_HOME 0x0800 | |
#define DSK_DSW_SECTOR_MASK 0x0003 | |
static t_stat dsk_svc (UNIT *uptr); | |
static t_stat dsk_reset (DEVICE *dptr); | |
static t_stat dsk_attach (UNIT *uptr, char *cptr); | |
static t_stat dsk_detach (UNIT *uptr); | |
static t_stat dsk_boot (int unitno); | |
static void diskfail (UNIT *uptr, int errflag); | |
/* DSK data structures | |
dsk_dev disk device descriptor | |
dsk_unit unit descriptor | |
dsk_reg register list | |
*/ | |
UNIT dsk_unit[] = { | |
{ UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) }, | |
{ UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) }, | |
{ UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) }, | |
{ UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) }, | |
{ UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) } | |
}; | |
#define IS_ONLINE(u) (((u)->flags & (UNIT_ATT|UNIT_DIS)) == UNIT_ATT) | |
/* Parameters in the unit descriptor */ | |
#define CYL u3 /* current cylinder */ | |
#define FUNC u4 /* current function */ | |
REG dsk_reg[] = { | |
{ HRDATA (DSKDSW0, dsk_dsw[0], 16) }, | |
{ HRDATA (DSKDSW1, dsk_dsw[1], 16) }, | |
{ HRDATA (DSKDSW2, dsk_dsw[2], 16) }, | |
{ HRDATA (DSKDSW3, dsk_dsw[3], 16) }, | |
{ HRDATA (DSKDSW4, dsk_dsw[4], 16) }, | |
{ DRDATA (STIME, dsk_swait, 24), PV_LEFT }, | |
{ DRDATA (RTIME, dsk_rwait, 24), PV_LEFT }, | |
{ NULL } }; | |
MTAB dsk_mod[] = { | |
{ UNIT_RONLY, 0, "write enabled", "ENABLED", NULL }, | |
{ UNIT_RONLY, UNIT_RONLY, "write locked", "LOCKED", NULL }, | |
{ 0 } }; | |
DEVICE dsk_dev = { | |
"DSK", dsk_unit, dsk_reg, dsk_mod, | |
DSK_NUMDR, 16, 16, 1, 16, 16, | |
NULL, NULL, &dsk_reset, | |
dsk_boot, dsk_attach, dsk_detach}; | |
static int32 dsk_ilswbit[DSK_NUMDR] = { /* interrupt level status word bits for the drives */ | |
ILSW_2_1131_DISK, | |
ILSW_2_2310_DRV_1, | |
ILSW_2_2310_DRV_2, | |
ILSW_2_2310_DRV_3, | |
ILSW_2_2310_DRV_4, | |
}; | |
static int32 dsk_ilswlevel[DSK_NUMDR] = | |
{ | |
2, /* interrupt levels for the drives */ | |
2, 2, 2, 2 | |
}; | |
/* xio_disk - XIO command interpreter for the disk drives */ | |
/* | |
* device status word: | |
* | |
* 0 data error, occurs when: | |
* 1. A modulo 4 error is detected during a read, read-check, or write operation. | |
* 2. The disk storage is in a read or write mode at the leading edge of a sector pulse. | |
* 3. A seek-incomplete signal is received from the 2311. | |
* 4. A write select error has occurred in the disk storage drive. | |
* 5. The power unsafe latch is set in the attachment. | |
* Conditions 1, 2, and 3 are turned off by a sense device command with modifier bit 15 | |
* set to 1. Conditions 4 and 5 are turned off by powering the drive off and back on. | |
* 1 operation complete | |
* 2 not ready, occurs when disk not ready or busy or disabled or off-line or | |
* power unsafe latch set. Also included in the disk not ready is the write select error, | |
* which can be a result of power unsafe or write select. | |
* 3 disk busy | |
* 4 carriage home (on cyl 0) | |
* 15-16: number of next sector spinning into position. | |
*/ | |
void xio_disk (int32 iocc_addr, int32 func, int32 modify, int drv) | |
{ | |
int i, rev, nsteps, newcyl, sec, nwords; | |
t_addr newpos; | |
char msg[80]; | |
UNIT *uptr = dsk_unit+drv; | |
int16 buf[DSK_NUMWD]; | |
if (! BETWEEN(drv, 0, DSK_NUMDR-1)) { // hmmm, invalid drive */ | |
if (func != XIO_SENSE_DEV) { // tried to use it, too | |
sprintf(msg, "Op %x on invalid drive number %d", func, drv); | |
xio_error(msg); | |
} | |
return; | |
} | |
CLRBIT(uptr->flags, UNIT_OPERR); /* clear pending error flag from previous op, if any */ | |
switch (func) { | |
case XIO_INITR: | |
if (! IS_ONLINE(uptr)) { /* disk is offline */ | |
diskfail(uptr, UNIT_HARDERR); /* make error stick till reset or attach */ | |
break; | |
} | |
sim_cancel(uptr); /* cancel any pending ops */ | |
dsk_dsw[drv] |= DSK_DSW_DISK_BUSY; /* and mark the disk as busy */ | |
nwords = M[iocc_addr++ & mem_mask]; /* get word count w/o upsetting SAR/SBR */ | |
if (nwords == 0) /* this is bad -- locks up disk controller ! */ | |
break; | |
nwords &= 1023; /* sanity check */ | |
if (! BETWEEN(nwords, 1, DSK_NUMWD)) { /* count bad */ | |
SETBIT(uptr->flags, UNIT_OPERR); /* set data error DSW bit when op complete */ | |
nwords = DSK_NUMWD; /* limit xfer to proper sector size */ | |
} | |
sec = modify & 0x07; /* get sector on cylinder */ | |
if ((modify & 0x0080) == 0) { /* it's real if not a read check */ | |
newpos = (uptr->CYL*DSK_NUMSC*DSK_NUMSF + sec)*2*DSK_NUMWD; | |
if (uptr->pos != newpos) | |
fseek(uptr->fileref, newpos, SEEK_SET); | |
fread(buf, 2, DSK_NUMWD, uptr->fileref); // read whole sector so we're in position for next read | |
uptr->pos = newpos + 2*DSK_NUMWD; | |
trace_io("* DSK%d read %d words from %d.%d (%x) to M[%04x-%04x]", uptr-dsk_unit, nwords, uptr->CYL, sec, newpos, iocc_addr & mem_mask, | |
(iocc_addr + nwords - 1) & mem_mask); | |
for (i = 0; i < nwords; i++) | |
M[iocc_addr++ & mem_mask] = buf[i]; | |
} | |
else | |
trace_io("* DSK%d verify %d.%d", uptr-dsk_unit, uptr->CYL, sec); | |
uptr->FUNC = func; | |
sim_activate(uptr, dsk_rwait); | |
break; | |
case XIO_INITW: | |
if (! IS_ONLINE(uptr)) { /* disk is offline */ | |
diskfail(uptr, UNIT_HARDERR); /* make error stick till reset or attach */ | |
break; | |
} | |
if (uptr->flags & UNIT_RONLY) { /* oops, write to RO disk? permanent error */ | |
diskfail(uptr, UNIT_HARDERR); | |
break; | |
} | |
sim_cancel(uptr); /* cancel any pending ops */ | |
dsk_dsw[drv] |= DSK_DSW_DISK_BUSY; /* and mark drive as busy */ | |
nwords = M[iocc_addr++ & mem_mask]; /* get word count w/o upsetting SAR/SBR */ | |
if (nwords == 0) /* this is bad -- locks up disk controller ! */ | |
break; | |
nwords &= 1023; /* sanity check */ | |
if (! BETWEEN(nwords, 1, DSK_NUMWD)) { /* count bad */ | |
SETBIT(uptr->flags, UNIT_OPERR); /* set data error DSW bit when op complete */ | |
nwords = DSK_NUMWD; /* limit xfer to proper sector size */ | |
} | |
sec = modify & 0x07; /* get sector on cylinder */ | |
newpos = (uptr->CYL*DSK_NUMSC*DSK_NUMSF + sec)*2*DSK_NUMWD; | |
if (uptr->pos != newpos) | |
fseek(uptr->fileref, newpos, SEEK_SET); | |
trace_io("* DSK%d wrote %d words from M[%04x-%04x] to %d.%d (%x)", uptr-dsk_unit, nwords, iocc_addr & mem_mask, (iocc_addr + nwords - 1) & mem_mask, uptr->CYL, sec, newpos); | |
for (i = 0; i < nwords; i++) | |
buf[i] = M[iocc_addr++ & mem_mask]; | |
for (; i < DSK_NUMWD; i++) /* rest of sector gets zeroed */ | |
buf[i] = 0; | |
fwrite(buf, 2, DSK_NUMWD, uptr->fileref); | |
uptr->pos = newpos + 2*DSK_NUMWD; | |
uptr->FUNC = func; | |
sim_activate(uptr, dsk_rwait); | |
break; | |
case XIO_CONTROL: /* step fwd/rev */ | |
if (! IS_ONLINE(uptr)) { | |
diskfail(uptr, UNIT_HARDERR); | |
break; | |
} | |
sim_cancel(uptr); | |
rev = modify & 4; | |
nsteps = iocc_addr & 0x00FF; | |
if (nsteps == 0) /* 0 steps does not cause op complete interrupt */ | |
break; | |
newcyl = uptr->CYL + (rev ? (-nsteps) : nsteps); | |
if (newcyl < 0) | |
newcyl = 0; | |
else if (newcyl >= DSK_NUMCY) | |
newcyl = DSK_NUMCY-1; | |
uptr->FUNC = func; | |
uptr->CYL = newcyl; | |
sim_activate(uptr, dsk_swait); /* schedule interrupt */ | |
dsk_dsw[drv] |= DSK_DSW_DISK_BUSY; | |
trace_io("* DSK%d at cyl %d", uptr-dsk_unit, newcyl); | |
break; | |
case XIO_SENSE_DEV: | |
CLRBIT(dsk_dsw[drv], DSK_DSW_CARRIAGE_HOME|DSK_DSW_NOT_READY); | |
if ((uptr->flags & UNIT_HARDERR) || (dsk_dsw[drv] & DSK_DSW_DISK_BUSY) || ! IS_ONLINE(uptr)) | |
SETBIT(dsk_dsw[drv], DSK_DSW_NOT_READY); | |
else if (uptr->CYL <= 0) { | |
SETBIT(dsk_dsw[drv], DSK_DSW_CARRIAGE_HOME); | |
uptr->CYL = 0; | |
} | |
dsk_sec[drv] = (dsk_sec[drv] + 1) % 4; /* advance the "next sector" count every time */ | |
ACC = dsk_dsw[drv] | dsk_sec[drv]; | |
if (modify & 0x01) { /* reset interrupts */ | |
CLRBIT(dsk_dsw[drv], DSK_DSW_OP_COMPLETE|DSK_DSW_DATA_ERROR); | |
CLRBIT(ILSW[dsk_ilswlevel[drv]], dsk_ilswbit[drv]); | |
} | |
break; | |
default: | |
sprintf(msg, "Invalid disk XIO function %x", func); | |
xio_error(msg); | |
} | |
} | |
/* diskfail - schedule an operation complete that sets the error bit */ | |
static void diskfail (UNIT *uptr, int errflag) | |
{ | |
sim_cancel(uptr); /* cancel any pending ops */ | |
SETBIT(uptr->flags, errflag); /* set the error flag */ | |
uptr->FUNC = XIO_FAILED; /* tell svc routine why it failed */ | |
sim_activate(uptr, 1); /* schedule an immediate op complete interrupt */ | |
} | |
t_stat dsk_svc (UNIT *uptr) | |
{ | |
int drv = uptr - dsk_unit; | |
CLRBIT(dsk_dsw[drv], DSK_DSW_DISK_BUSY); /* activate operation complete interrupt */ | |
SETBIT(dsk_dsw[drv], DSK_DSW_OP_COMPLETE); | |
if (uptr->flags & (UNIT_OPERR|UNIT_HARDERR)) { /* word count error or data error */ | |
SETBIT(dsk_dsw[drv], DSK_DSW_DATA_ERROR); | |
CLRBIT(uptr->flags, UNIT_OPERR); /* but don't clear hard error */ | |
} | |
SETBIT(ILSW[dsk_ilswlevel[drv]], dsk_ilswbit[drv]); | |
#ifdef XXXX | |
switch (uptr->FUNC) { | |
case XIO_CONTROL: | |
case XIO_INITR: | |
case XIO_INITW: | |
case XIO_FAILED: | |
break; | |
default: | |
fprintf(stderr, "Unexpected FUNC %x in dsk_svc(%d)\n", uptr->FUNC, drv); | |
break; | |
} | |
uptr->FUNC = -1; // we're done with this operation | |
#endif | |
return SCPE_OK; | |
} | |
t_stat dsk_reset (DEVICE *dptr) | |
{ | |
int drv; | |
UNIT *uptr; | |
for (drv = 0, uptr = dsk_dev.units; drv < DSK_NUMDR; drv++, uptr++) { | |
sim_cancel(uptr); | |
CLRBIT(ILSW[2], dsk_ilswbit[drv]); | |
CLRBIT(uptr->flags, UNIT_OPERR|UNIT_HARDERR); | |
uptr->CYL = 0; | |
uptr->FUNC = -1; | |
dsk_dsw[drv] = (uptr->flags & UNIT_ATT) ? DSK_DSW_CARRIAGE_HOME : 0; | |
} | |
calc_ints(); | |
return SCPE_OK; | |
} | |
static t_stat dsk_attach (UNIT *uptr, char *cptr) | |
{ | |
int drv = uptr - dsk_unit; | |
t_stat rval; | |
sim_cancel(uptr); | |
if ((rval = attach_unit(uptr, cptr)) != SCPE_OK) | |
return rval; | |
CLRBIT(ILSW[2], dsk_ilswbit[drv]); | |
CLRBIT(uptr->flags, UNIT_OPERR|UNIT_HARDERR); | |
calc_ints(); | |
uptr->CYL = 0; | |
uptr->FUNC = -1; | |
dsk_dsw[drv] = DSK_DSW_CARRIAGE_HOME; | |
if (drv == 0) { | |
disk_ready(TRUE); | |
disk_unlocked(FALSE); | |
} | |
return SCPE_OK; | |
} | |
static t_stat dsk_detach (UNIT *uptr) | |
{ | |
t_stat rval; | |
int drv = uptr - dsk_unit; | |
sim_cancel(uptr); | |
if ((rval = detach_unit (uptr)) != SCPE_OK) | |
return rval; | |
CLRBIT(ILSW[2], dsk_ilswbit[drv]); | |
CLRBIT(uptr->flags, UNIT_OPERR|UNIT_HARDERR); | |
calc_ints(); | |
uptr->CYL = 0; | |
uptr->FUNC = -1; | |
dsk_dsw[drv] = 0; | |
if (drv == 0) { | |
disk_unlocked(TRUE); | |
disk_ready(FALSE); | |
} | |
return SCPE_OK; | |
} | |
// boot routine - if they type BOOT DSK, load the standard boot card. | |
static t_stat dsk_boot (int unitno) | |
{ | |
t_stat rval; | |
if ((rval = reset_all(0)) != SCPE_OK) | |
return rval; | |
return load_cr_boot(unitno); | |
} |