| /* 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); | |
| } |