/* | |
Copyright (c) 2015-2016, John Forecast | |
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 | |
JOHN FORECAST 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 John Forecast shall not | |
be used in advertising or otherwise to promote the sale, use or other dealings | |
in this Software without prior written authorization from John Forecast. | |
*/ | |
/* cdc1700_dp.c: disk pack controller support | |
* Simh devices: dp0, dp1 | |
*/ | |
#include "cdc1700_defs.h" | |
#define ADDRSTATUS iod_readR[2] | |
extern char INTprefix[]; | |
extern void RaiseExternalInterrupt(DEVICE *); | |
extern t_bool doDirectorFunc(DEVICE *, t_bool); | |
extern t_bool fw_reject(IO_DEVICE *, t_bool, uint8); | |
extern void fw_IOunderwayEOP(IO_DEVICE *, uint16); | |
extern void fw_IOcompleteEOP(t_bool, DEVICE *, IO_DEVICE *, uint16, const char *); | |
extern void fw_IOalarm(t_bool, DEVICE *, IO_DEVICE *, const char *); | |
extern void fw_IOintr(t_bool, DEVICE *, IO_DEVICE *, uint16, uint16, uint16, const char *); | |
extern t_stat checkReset(DEVICE *, uint8); | |
extern t_stat show_addr(FILE *, UNIT *, int32, CONST void *); | |
extern t_stat set_protected(UNIT *, int32, CONST char *, void *); | |
extern t_stat clear_protected(UNIT *, int32, CONST char *, void *); | |
extern t_stat set_equipment(UNIT *, int32, CONST char *, void *); | |
extern t_stat set_stoponrej(UNIT *, int32, CONST char *, void *); | |
extern t_stat clr_stoponrej(UNIT *, int32, CONST char *, void *); | |
extern uint16 LoadFromMem(uint16); | |
extern t_bool IOStoreToMem(uint16, uint16, t_bool); | |
extern uint16 M[], Areg, IOAreg; | |
extern t_bool IOFWinitialized; | |
extern t_bool ExecutionStarted; | |
extern UNIT cpu_unit; | |
static t_stat show_drive(FILE *, UNIT *, int32, CONST void *); | |
t_stat set_dp853(UNIT *, int32, CONST char *, void *); | |
t_stat set_dp854(UNIT *, int32, CONST char *, void *); | |
static t_stat show_addressing(FILE *, UNIT *, int32, CONST void *); | |
t_stat set_normal(UNIT *, int32, CONST char *, void *); | |
t_stat set_reverse(UNIT *, int32, CONST char *, void *); | |
/* Constants */ | |
#define DP_NUMWD (96) /* words/sector */ | |
#define DP_NUMBY (DP_NUMWD * sizeof(uint16)) | |
#define DP_NUMSC (16) /* sectors/track */ | |
#define DP_NUMTR (10) /* tracks/cylinder */ | |
#define DP_853CY (100) /* cylinders for 853 drive */ | |
#define DP_854CY (203) /* cylinders for 854 drive */ | |
#define DP853_SIZE (DP_853CY * DP_NUMTR * DP_NUMSC * DP_NUMBY) | |
#define DP854_SIZE (DP_854CY * DP_NUMTR * DP_NUMSC * DP_NUMBY) | |
#define DPLBA(i) \ | |
((i->cylinder * DP_NUMSC * DP_NUMTR) + (i->head * DP_NUMSC) + i->sector) | |
#define DP_NUMDR 2 /* # drives */ | |
struct dpio_unit { | |
uint16 state; /* Current state of the drive */ | |
#define DP_IDLE 0x0000 /* Idle */ | |
#define DP_XFER 0x0001 /* Control info transfer */ | |
#define DP_SEEK 0x0002 /* Seeking */ | |
#define DP_WRITE 0x0003 /* Write data */ | |
#define DP_READ 0x0004 /* Read data */ | |
#define DP_COMPARE 0x0005 /* Compare data */ | |
#define DP_CHECKWORD 0x0006 /* Checkword check (NOOP) */ | |
#define DP_WRITEADDR 0x0007 /* Write address (NOOP) */ | |
uint16 CWA; /* Current memory address */ | |
uint16 LWA; /* LWA + 1 for transfer */ | |
uint16 sectorRA; /* Sector Record Address */ | |
uint16 cylinder; /* Current cylinder # */ | |
uint16 head; /* Current head # */ | |
uint16 sector; /* Current sector # */ | |
uint16 buf[DP_NUMWD]; /* Sector buffer */ | |
t_bool oncyl; /* Unit on-cylinder status */ | |
} DPunits[DP_NUMDR]; | |
t_bool DPbusy = FALSE; /* Controller vs. unit busy */ | |
enum dpio_status { | |
DPIO_MORE, /* More I/O pending */ | |
DPIO_DONE, /* I/O processing completed */ | |
DPIO_PROTECT, /* Protect fault */ | |
DPIO_MISMATCH, /* Compare mismatch */ | |
DPIO_ADDRERR /* Addressing error */ | |
}; | |
t_stat dp_svc(UNIT *); | |
t_stat dp_reset(DEVICE *); | |
t_stat dp_attach(UNIT *, CONST char *); | |
t_stat dp_detach(UNIT *); | |
void DPstate(const char *, DEVICE *, IO_DEVICE *); | |
t_bool DPreject(IO_DEVICE *, t_bool, uint8); | |
enum IOstatus DPin(IO_DEVICE *, uint8); | |
enum IOstatus DPout(IO_DEVICE *, uint8); | |
t_bool DPintr(IO_DEVICE *); | |
t_stat dp_help(FILE *, DEVICE *, UNIT *, int32, const char *); | |
/* | |
1738-B Disk Pack Controller | |
Addresses | |
Computer Instruction | |
Q Register Output From A Input to A | |
(Bits 02-00) | |
001 Director Function Director Status | |
010 Load Address Address Register Status | |
011 Write | |
100 Read | |
101 Compare | |
110 Checkword Check | |
111 Write Address | |
Operations: | |
Director Function | |
15 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| X | X | X | X | X | X | | | | X | X | | | | | X | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | | |
| | | | | | Clr Interrupts | |
| | | | | Ready and not Busy | |
| | | | | Interrupt Req. | |
| | | | EOP Interrupt Req. | |
| | | Interrupt on Alarm | |
| | Release | |
| Unit Select | |
Unit Select Code | |
Load Address, Checkword Check, Write Address or Address Register Status | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | | | | | | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | |
+---------------------------+ +-----------+ +-----------+ | |
Cylinder Head Sector | |
853: 0-99 0-9 0-15 | |
854: 0-202 | |
Write, Read or Compare | |
15 14 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| X | | | | | | | | | | | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | |
+-------------------------------------------------------+ | |
FWA - 1 | |
Status Response: | |
Director Status | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| X | | | | | | | | | | | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | Ready | |
| | | | | | | | | | | | | Busy | |
| | | | | | | | | | | | Interrupt | |
| | | | | | | | | | | On Cylinder | |
| | | | | | | | | | End of Operation | |
| | | | | | | | | Alarm | |
| | | | | | | | No Compare | |
| | | | | | | Protected | |
| | | | | | Checkword Error | |
| | | | | Lost Data | |
| | | | Seek Error | |
| | | Address Error | |
| | Defective Track | |
| Storage Parity Error | |
Protect Fault | |
Address Register Status | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | | | | | | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | | | | | | |
+---------------------------+ +-----------+ +-----------+ | |
Cylinder Head Sector | |
853: 0-99 0-9 0-15 | |
854: 0-202 | |
*/ | |
IO_DEVICE DPdev = IODEV(NULL, "1738-B", 1738, 3, 0xFF, 0, | |
DPreject, DPin, DPout, NULL, NULL, | |
DPstate, DPintr, NULL, NULL, | |
0x7F, 8, | |
MASK_REGISTER1 | MASK_REGISTER2 | MASK_REGISTER3 | \ | |
MASK_REGISTER4 | MASK_REGISTER5 | MASK_REGISTER6 | \ | |
MASK_REGISTER7, | |
MASK_REGISTER1 | MASK_REGISTER2, | |
MASK_REGISTER0, MASK_REGISTER0, | |
0, 0, DPunits); | |
/* DP data structures | |
dp_dev DP device descriptor | |
dp_unit DP units | |
dp_reg DP register list | |
dp_mod DP modifier list | |
*/ | |
UNIT dp_unit[] = { | |
{ UDATA(&dp_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_854, DP854_SIZE), | |
0, 0, 0, 0, 0, &DPunits[0] | |
}, | |
{ UDATA(&dp_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_854, DP854_SIZE), | |
0, 0, 0, 0, 0, &DPunits[1] | |
}, | |
}; | |
REG dp_reg[] = { | |
{ HRDATAD(FUNCTION, DPdev.FUNCTION, 16, "Last director function issued") }, | |
{ HRDATAD(STATUS, DPdev.STATUS, 16, "Director status register") }, | |
{ HRDATAD(IENABLE, DPdev.IENABLE, 16, "Interrupts enabled") }, | |
{ HRDATAD(ADDRSTATUS, DPdev.ADDRSTATUS, 16, "Address register status") }, | |
{ NULL } | |
}; | |
MTAB dp_mod[] = { | |
{ MTAB_XTD|MTAB_VDV, 0, "1738-B Disk Pack Controller" }, | |
{ MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", "EQUIPMENT=hexAddress", | |
&set_equipment, &show_addr, NULL, "Display equipment address" }, | |
{ MTAB_XTD|MTAB_VUN, 0, "DRIVE", NULL, | |
NULL, &show_drive, NULL, "Display type of drive (853 or 854" }, | |
{ MTAB_XTD|MTAB_VUN, 0, NULL, "853", | |
&set_dp853, NULL, NULL, "Set drive type to 853" }, | |
{ MTAB_XTD|MTAB_VUN, 0, NULL, "854", | |
&set_dp854, NULL, NULL, "Set drive type to 854" }, | |
{ MTAB_XTD|MTAB_VDV, 0, NULL, "STOPONREJECT", | |
&set_stoponrej, NULL, NULL, "Stop simulation if I/O is rejected" }, | |
{ MTAB_XTD|MTAB_VDV, 0, NULL, "NOSTOPONREJECT", | |
&clr_stoponrej, NULL, NULL, "Don't stop simulation if I/O is rejected" }, | |
{ MTAB_XTD|MTAB_VDV, 0, NULL,"PROTECT", | |
&set_protected, NULL, NULL, "Device is protected (unimplemented)" }, | |
{ MTAB_XTD|MTAB_VDV, 0, NULL, "NOPROTECT", | |
&clear_protected, NULL, NULL, "Device is unprotected (unimplemented)"}, | |
{ MTAB_XTD|MTAB_VDV, 0, "ADDRESSING", NULL, | |
NULL, &show_addressing, NULL, "Display disk addressing mode" }, | |
{ MTAB_XTD|MTAB_VDV, 0, NULL, "NORMAL", | |
&set_normal, NULL, NULL, "Normal addressing mode: drive 0 then 1" }, | |
{ MTAB_XTD|MTAB_VDV, 0, NULL, "REVERSE", | |
&set_reverse, NULL, NULL, "Reverse addressing mode: drive 1 then 0" }, | |
{ 0 } | |
}; | |
DEBTAB dp_deb[] = { | |
{ "TRACE", DBG_DTRACE, "Trace device I/O requests" }, | |
{ "STATE", DBG_DSTATE, "Display device state changes" }, | |
{ "INTR", DBG_DINTR, "Display device interrupt requests" }, | |
{ "ERROR", DBG_DERROR, "Display device errors" }, | |
{ "LOCATION", DBG_DLOC, "Display address of I/O instructions" }, | |
{ "FIRSTREJ", DBG_DFIRSTREJ, "Suppress display of 2nd ... I/O rejects" }, | |
{ "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DINTR | DBG_DERROR | DBG_DLOC }, | |
{ NULL } | |
}; | |
DEVICE dp_dev = { | |
"DP", dp_unit, dp_reg, dp_mod, | |
DP_NUMDR, 10, 31, 1, 8, 8, | |
NULL, NULL, &dp_reset, | |
NULL, &dp_attach, &dp_detach, | |
&DPdev, | |
DEV_DEBUG | DEV_DISK | DEV_DISABLE | \ | |
DEV_DIS | DEV_INDEV | DEV_OUTDEV | DEV_PROTECT, | |
0, dp_deb, | |
NULL, NULL, &dp_help, NULL, NULL, NULL | |
}; | |
/* | |
* Display disk pack drive type | |
*/ | |
static t_stat show_drive(FILE *st, UNIT *uptr, int32 val, CONST void *desc) | |
{ | |
if (uptr == NULL) | |
return SCPE_IERR; | |
if ((uptr->flags & UNIT_854) != 0) | |
fprintf(st, "854 drive"); | |
else fprintf(st, "853 drive"); | |
return SCPE_OK; | |
} | |
/* | |
* Set drive type to 853. If execution has started, disallow device type | |
* changes. | |
*/ | |
t_stat set_dp853(UNIT *uptr, int32 val, CONST char *cptr, void *desc) | |
{ | |
if (uptr == NULL) | |
return SCPE_IERR; | |
if ((uptr->flags & UNIT_854) != 0) { | |
if ((uptr->flags & UNIT_ATT) != 0) | |
return SCPE_ALATT; | |
if (ExecutionStarted) | |
return sim_messagef(SCPE_IERR, "Unable to change drive type after execution started\n"); | |
uptr->flags &= ~UNIT_854; | |
uptr->capac = DP853_SIZE; | |
} | |
return SCPE_OK; | |
} | |
/* | |
* Set drive type to 854. If execution has started, disallow device type | |
* changes. | |
*/ | |
t_stat set_dp854(UNIT *uptr, int32 val, CONST char *cptr, void *desc) | |
{ | |
if (uptr == NULL) | |
return SCPE_IERR; | |
if ((uptr->flags & UNIT_854) == 0) { | |
if ((uptr->flags & UNIT_ATT) != 0) | |
return SCPE_ALATT; | |
if (ExecutionStarted) | |
return sim_messagef(SCPE_IERR, "Unable to change drive type after execution started\n"); | |
uptr->flags |= UNIT_854; | |
uptr->capac = DP854_SIZE; | |
} | |
return SCPE_OK; | |
} | |
/* | |
* Display the device addressing mode | |
*/ | |
static t_stat show_addressing(FILE *st, UNIT *uptr, int32 val, CONST void *desc) | |
{ | |
if (uptr == NULL) | |
return SCPE_IERR; | |
if ((dp_dev.flags & DEV_REVERSE) == 0) | |
fprintf(st, "Addressing: Normal"); | |
else fprintf(st, "Addressing: Reverse"); | |
return SCPE_OK; | |
} | |
/* | |
* Set device to normal addressing. | |
*/ | |
t_stat set_normal(UNIT *uptr, int32 val, CONST char *cptr, void *desc) | |
{ | |
if (uptr == NULL) | |
return SCPE_IERR; | |
dp_dev.flags &= ~DEV_REVERSE; | |
return SCPE_OK; | |
} | |
/* | |
* Set device to reverse addressing. | |
*/ | |
t_stat set_reverse(UNIT *uptr, int32 val, CONST char *cptr, void *desc) | |
{ | |
if (uptr == NULL) | |
return SCPE_IERR; | |
dp_dev.flags |= DEV_REVERSE; | |
return SCPE_OK; | |
} | |
/* | |
* Dump the current internal state of the DP device. | |
*/ | |
const char *DPstateStr[] = { | |
"Idle", "Xfer", "Seek", "Write", "Read", "Compare", "Checkword", "WriteAddr" | |
}; | |
void DPstate(const char *where, DEVICE *dev, IO_DEVICE *iod) | |
{ | |
fprintf(DBGOUT, | |
"%s[%s %s: Func: %04X, Sta: %04X, Ena: %04X, Sel: %s, Busy: %s]\r\n", | |
INTprefix, dev->name, where, | |
iod->FUNCTION, iod->STATUS, iod->IENABLE, | |
iod->iod_unit == NULL ? "None" : | |
(iod->iod_unit == dev->units ? "0" : "1"), | |
DPbusy ? "Yes" : "No"); | |
if ((dp_unit[0].flags & UNIT_ATT) != 0) | |
fprintf(DBGOUT, | |
"%s[0: State: %s, Cur: %04X, Last: %04X, RA: %04X, Oncyl: %s]\r\n", | |
INTprefix, DPstateStr[DPunits[0].state], DPunits[0].CWA, | |
DPunits[0].LWA, DPunits[0].sectorRA, | |
DPunits[0].oncyl ? "Yes" : "No"); | |
if ((dp_unit[1].flags & UNIT_ATT) != 0) | |
fprintf(DBGOUT, | |
"%s[1: State: %s, Cur: %04X, Last: %04X, RA: %04X, Oncyl: %s]\r\n", | |
INTprefix, DPstateStr[DPunits[1].state], DPunits[1].CWA, | |
DPunits[1].LWA, DPunits[1].sectorRA, | |
DPunits[1].oncyl ? "Yes" : "No"); | |
} | |
/* | |
* Determine if a non-standard interrupt condition is present. | |
*/ | |
t_bool DPintr(IO_DEVICE *iod) | |
{ | |
return (ISENABLED(iod, IO_1738_RBINT) && | |
((DEVSTATUS(iod) & (IO_ST_READY | IO_ST_BUSY)) == IO_ST_READY)); | |
} | |
/* | |
* Load and validate disk address in the A register | |
*/ | |
static t_bool LoadDiskAddress(UNIT *uptr, struct dpio_unit *iou, uint16 state) | |
{ | |
uint16 numcy = ((uptr->flags & UNIT_854) != 0) ? DP_854CY : DP_853CY; | |
iou->oncyl = FALSE; | |
DPdev.ADDRSTATUS = iou->sectorRA = IOAreg; | |
/* | |
* Split the disk address into separate fields. | |
*/ | |
iou->cylinder = (IOAreg >> 8) & 0xFF; | |
iou->head = (IOAreg >> 4) & 0xF; | |
iou->sector = IOAreg & 0xF; | |
if ((iou->cylinder >= numcy) || (iou->head >= DP_NUMTR)) | |
return FALSE; | |
iou->state = state; | |
return TRUE; | |
} | |
/* | |
* Set up a disk I/O operation with the A register containing FWA - 1. | |
*/ | |
static void StartDPDiskIO(UNIT *uptr, struct dpio_unit *iou, uint16 state) | |
{ | |
iou->LWA = LoadFromMem(IOAreg); | |
iou->CWA = ++IOAreg; | |
DPbusy = TRUE; | |
DPdev.STATUS &= IO_ST_READY | IO_ST_PROT | IO_1738_ONCYL; | |
fw_IOunderwayEOP(&DPdev, 0); | |
if ((dp_dev.dctrl & DBG_DTRACE) != 0) | |
fprintf(DBGOUT, | |
"%sDP - Start I/O, current: %04X, last: %04X, state: %d\r\n", | |
INTprefix, iou->CWA, iou->LWA, state); | |
if (iou->CWA == iou->LWA) { | |
/* | |
* This is an empty I/O request, complete it immediately. | |
*/ | |
DPbusy = FALSE; | |
if ((dp_dev.dctrl & DBG_DTRACE) != 0) | |
fprintf(DBGOUT, "%sDP - Empty I/O request\r\n", INTprefix); | |
fw_IOcompleteEOP(FALSE, &dp_dev, &DPdev, 0xFFFF, "Null transfer complete"); | |
return; | |
} | |
iou->state = state; | |
sim_activate(uptr, DP_IO_WAIT); | |
} | |
/* | |
* Increment sector # and update Sector Record Address. | |
*/ | |
static void DPDiskIOIncSector(struct dpio_unit *iou) | |
{ | |
if (++iou->sector >= DP_NUMSC) { | |
iou->sector = 0; | |
if (++iou->head >= DP_NUMTR) { | |
iou->head = 0; | |
iou->cylinder++; | |
} | |
} | |
iou->sectorRA = ((iou->cylinder << 8) | (iou->head << 4)) | iou->sector; | |
DPdev.ADDRSTATUS = iou->sectorRA; | |
} | |
/* | |
* Initiate a read operation on a disk. | |
*/ | |
static enum dpio_status DPDiskIORead(UNIT *uptr) | |
{ | |
struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; | |
uint16 numcy = ((uptr->flags & UNIT_854) != 0) ? DP_854CY : DP_853CY; | |
uint32 lba = DPLBA(iou); | |
int i; | |
if (iou->cylinder >= numcy) | |
return DPIO_ADDRERR; | |
sim_fseeko(uptr->fileref, lba * DP_NUMBY, SEEK_SET); | |
sim_fread(iou->buf, sizeof(uint16), DP_NUMWD, uptr->fileref); | |
for (i = 0; i < DP_NUMWD; i++) { | |
/*** TODO: fix protect check ***/ | |
if (!IOStoreToMem(iou->CWA, iou->buf[i], TRUE)) | |
return DPIO_PROTECT; | |
iou->CWA++; | |
if (iou->CWA == iou->LWA) { | |
DPDiskIOIncSector(iou); | |
return DPIO_DONE; | |
} | |
} | |
DPDiskIOIncSector(iou); | |
return DPIO_MORE; | |
} | |
/* | |
* Initiate a write operation on a disk. | |
*/ | |
static enum dpio_status DPDiskIOWrite(UNIT *uptr) | |
{ | |
struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; | |
uint16 numcy = ((uptr->flags & UNIT_854) != 0) ? DP_854CY : DP_853CY; | |
uint32 lba = DPLBA(iou); | |
t_bool fill = FALSE; | |
int i; | |
if (iou->cylinder >= numcy) | |
return DPIO_ADDRERR; | |
for (i = 0; i < DP_NUMWD; i++) { | |
if (!fill) { | |
iou->buf[i] = LoadFromMem(iou->CWA); | |
iou->CWA++; | |
if (iou->CWA == iou->LWA) | |
fill = TRUE; | |
} else iou->buf[i] = 0; | |
} | |
sim_fseeko(uptr->fileref, lba * DP_NUMBY, SEEK_SET); | |
sim_fwrite(iou->buf, sizeof(uint16), DP_NUMWD, uptr->fileref); | |
DPDiskIOIncSector(iou); | |
return fill ? DPIO_DONE : DPIO_MORE; | |
} | |
/* | |
* Initiate a compare operation on a disk. | |
*/ | |
static enum dpio_status DPDiskIOCompare(UNIT *uptr) | |
{ | |
struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; | |
uint16 numcy = ((uptr->flags & UNIT_854) != 0) ? DP_854CY : DP_853CY; | |
uint32 lba = DPLBA(iou); | |
int i; | |
if (iou->cylinder >= numcy) | |
return DPIO_ADDRERR; | |
sim_fseeko(uptr->fileref, lba * DP_NUMBY, SEEK_SET); | |
sim_fread(iou->buf, sizeof(uint16), DP_NUMWD, uptr->fileref); | |
for (i = 0; i < DP_NUMWD; i++) { | |
if (iou->buf[i] != LoadFromMem(iou->CWA)) | |
return DPIO_MISMATCH; | |
iou->CWA++; | |
if (iou->CWA == iou->LWA) { | |
DPDiskIOIncSector(iou); | |
return DPIO_DONE; | |
} | |
} | |
DPDiskIOIncSector(iou); | |
return DPIO_MORE; | |
} | |
/* | |
* Perform read/write/compare sector operations from within the unit | |
* service routine. | |
*/ | |
void DPDiskIO(UNIT *uptr, uint16 iotype) | |
{ | |
struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; | |
const char *error = "Unknown"; | |
enum dpio_status status; | |
switch (iotype) { | |
case DP_WRITE: | |
status = DPDiskIOWrite(uptr); | |
break; | |
case DP_READ: | |
status = DPDiskIORead(uptr); | |
break; | |
case DP_COMPARE: | |
status = DPDiskIOCompare(uptr); | |
break; | |
} | |
switch (status) { | |
case DPIO_MORE: | |
sim_activate(uptr, DP_IO_WAIT); | |
break; | |
case DPIO_PROTECT: | |
DPdev.STATUS |= IO_1738_SPROT; | |
error = "Protection Fault"; | |
goto err; | |
case DPIO_ADDRERR: | |
DPdev.STATUS |= IO_1738_ADDRERR; | |
error = "Address Error"; | |
err: | |
iou->state = DP_IDLE; | |
DPbusy = FALSE; | |
if ((dp_dev.dctrl & DBG_DERROR) != 0) | |
fprintf(DBGOUT, | |
"%sDP - Read/Write/Compare failed - %s\r\n", | |
INTprefix, error); | |
fw_IOalarm(FALSE, &dp_dev, &DPdev, "Alarm"); | |
break; | |
case DPIO_MISMATCH: | |
DPdev.STATUS |= IO_1738_NOCOMP; | |
/* FALLTHROUGH */ | |
case DPIO_DONE: | |
iou->state = DP_IDLE; | |
DPbusy = FALSE; | |
if ((dp_dev.dctrl & DBG_DTRACE) != 0) | |
fprintf(DBGOUT, | |
"%sDP - Read/Write/Compare transfer complete\r\n", INTprefix); | |
fw_IOcompleteEOP(TRUE, &dp_dev, &DPdev, 0xFFFF, "Transfer complete"); | |
break; | |
} | |
} | |
/* Unit service */ | |
t_stat dp_svc(UNIT *uptr) | |
{ | |
struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; | |
if ((dp_dev.dctrl & DBG_DTRACE) != 0) { | |
fprintf(DBGOUT, "%s[DP: dp_svc() entry]\r\n", INTprefix); | |
if ((dp_dev.dctrl & DBG_DSTATE) != 0) | |
DPstate("svc_entry", &dp_dev, &DPdev); | |
} | |
switch (iou->state) { | |
case DP_IDLE: | |
/* | |
* Unit is idle, nothing to do. | |
*/ | |
break; | |
case DP_XFER: | |
/* | |
* Transfer of positioning information is complete. | |
*/ | |
iou->state = DP_SEEK; | |
sim_activate(uptr, DP_SEEK_WAIT); | |
/* | |
* If this is the currently selected unit, update controller status | |
* and possibly ganerate an interrupt. | |
*/ | |
if (DPdev.iod_unit == uptr) { | |
DPdev.STATUS |= IO_ST_EOP; | |
if ((dp_dev.dctrl & DBG_DTRACE) != 0) | |
fprintf(DBGOUT, | |
"%sDP - Load Address positioning transfer complete\r\n", | |
INTprefix); | |
fw_IOintr(FALSE, &dp_dev, &DPdev, 0, 0, 0xFFFF, "Load address"); | |
} | |
break; | |
case DP_SEEK: | |
iou->state = DP_IDLE; | |
iou->oncyl = TRUE; | |
DPdev.STATUS &= ~IO_ST_BUSY; | |
/* | |
* If this is the currently selected unit, update controller status | |
* and possibly generate an interrupt. | |
*/ | |
if (DPdev.iod_unit == uptr) { | |
DPdev.STATUS |= IO_1738_ONCYL; | |
if ((dp_dev.dctrl & DBG_DTRACE) != 0) | |
fprintf(DBGOUT, "%sDP - Seek complete\r\n", INTprefix); | |
fw_IOintr(TRUE, &dp_dev, &DPdev, 0, 0, 0xFFFF, "Seek complete"); | |
} | |
break; | |
case DP_WRITE: | |
case DP_READ: | |
case DP_COMPARE: | |
DPDiskIO(uptr, iou->state); | |
break; | |
case DP_CHECKWORD: | |
iou->state = DP_IDLE; | |
iou->oncyl = TRUE; | |
/* | |
* Set Sector Record Address to the start of the next track. | |
*/ | |
iou->sector = 0; | |
if (++iou->head >= DP_NUMTR) { | |
iou->head = 0; | |
iou->cylinder++; | |
} | |
iou->sectorRA = ((iou->cylinder << 8) | (iou->head << 4)) | iou->sector; | |
DPdev.ADDRSTATUS = iou->sectorRA; | |
DPdev.STATUS |= IO_ST_EOP | IO_1738_ONCYL; | |
DPdev.STATUS &= ~IO_ST_BUSY; | |
DPbusy = FALSE; | |
if ((dp_dev.dctrl & DBG_DTRACE) != 0) | |
fprintf(DBGOUT, "%sDP - Checkword transfer complete\r\n", INTprefix); | |
if ((DPdev.STATUS & (IO_ST_READY | IO_ST_BUSY)) == IO_ST_READY) | |
fw_IOintr(TRUE, &dp_dev, &DPdev, 0, 0, 0xFFFF, "Checkword transfer"); | |
fw_IOintr(FALSE, &dp_dev, &DPdev, 0, 0, 0xFFFF, "Checkword"); | |
break; | |
case DP_WRITEADDR: | |
break; | |
} | |
if ((dp_dev.dctrl & DBG_DTRACE) != 0) { | |
fprintf(DBGOUT, "%s[DP: dp_svc() exit]\r\n", INTprefix); | |
if ((dp_dev.dctrl & DBG_DSTATE) != 0) | |
DPstate("svc_exit", &dp_dev, &DPdev); | |
} | |
return SCPE_OK; | |
} | |
/* Reset routine */ | |
t_stat dp_reset(DEVICE *dptr) | |
{ | |
t_stat r; | |
if (IOFWinitialized) | |
if ((dptr->flags & DEV_DIS) == 0) | |
if ((r = checkReset(dptr, DPdev.iod_equip)) != SCPE_OK) | |
return r; | |
DPbusy = FALSE; | |
/* | |
* Cancel any existing unit selection. | |
*/ | |
DPdev.iod_unit = NULL; | |
/* | |
* Clear on-cylinder status | |
*/ | |
DPdev.STATUS &= ~IO_1738_ONCYL; | |
return SCPE_OK; | |
} | |
/* Attach routine */ | |
t_stat dp_attach(UNIT *uptr, CONST char *cptr) | |
{ | |
struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; | |
const char *drivetype = ((uptr->flags & UNIT_854) != 0) ? "854" : "853"; | |
t_addr capac = ((uptr->flags & UNIT_854) != 0) ? DP854_SIZE : DP853_SIZE; | |
t_stat r; | |
uptr->capac = capac; | |
r = attach_unit(uptr, cptr); | |
if (r != SCPE_OK) | |
return r; | |
/* | |
* If this is a newly created file, set the drive size appropriately. | |
*/ | |
if (sim_fsize_ex(uptr->fileref) == 0) | |
sim_set_fsize(uptr->fileref, capac); | |
if (sim_fsize_ex(uptr->fileref) != capac) { | |
if (ExecutionStarted) { | |
detach_unit(uptr); | |
return sim_messagef(SCPE_OPENERR, "Unable to autosize drive after execution started\n"); | |
} | |
/* | |
* This is not the correct size according the drive type but this is the | |
* first attach. Force the drive to match the size of the disk. | |
*/ | |
switch (sim_fsize_ex(uptr->fileref)) { | |
case DP854_SIZE: | |
uptr->capac = DP854_SIZE; | |
uptr->flags |= UNIT_854; | |
break; | |
case DP853_SIZE: | |
uptr->capac = DP853_SIZE; | |
uptr->flags &= ~UNIT_854; | |
break; | |
default: | |
detach_unit(uptr); | |
return sim_messagef(SCPE_OPENERR, "Unsupported disk size\n"); | |
} | |
} | |
/* | |
* Set unit to cylinder 0, head 0, sector 0 and indicate on-cylinder. | |
*/ | |
iou->cylinder = 0; | |
iou->head = 0; | |
iou->sector = 0; | |
iou->oncyl = TRUE; | |
return SCPE_OK; | |
} | |
/* Detach routine */ | |
t_stat dp_detach(UNIT *uptr) | |
{ | |
struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; | |
t_stat stat; | |
sim_cancel(uptr); | |
stat = detach_unit(uptr); | |
iou->oncyl = FALSE; | |
return stat; | |
} | |
/* Check if I/O should be rejected */ | |
t_bool DPreject(IO_DEVICE *iod, t_bool output, uint8 reg) | |
{ | |
if (output) { | |
switch (reg) { | |
/* | |
* Director function | |
*/ | |
case 0x01: | |
/*** TODO: Check protect status ***/ | |
return DPbusy; | |
/* | |
* Write Address - always unsupported | |
*/ | |
case 0x07: | |
return TRUE; | |
/* | |
* Write/Checkword Check | |
*/ | |
case 0x03: | |
case 0x06: | |
/*** TODO: Check protect status ***/ | |
/* FALLTHROUGH */ | |
/* | |
* Load Address/Read/Compare | |
*/ | |
case 0x02: | |
case 0x04: | |
case 0x05: | |
return ((DEVSTATUS(iod) & | |
(IO_ST_READY | IO_ST_BUSY | IO_1738_ONCYL)) != | |
(IO_ST_READY | IO_1738_ONCYL)); | |
} | |
} | |
return FALSE; | |
} | |
/* Perform I/O */ | |
enum IOstatus DPin(IO_DEVICE *iod, uint8 reg) | |
{ | |
/* | |
* All input requests should be handled by the I/O framework. | |
*/ | |
return IO_REJECT; | |
} | |
enum IOstatus DPout(IO_DEVICE *iod, uint8 reg) | |
{ | |
UNIT *uptr; | |
struct dpio_unit *iou; | |
switch (reg) { | |
/* | |
* Director function | |
*/ | |
case 0x01: | |
/* | |
* Reject the request if both select and release are set | |
*/ | |
if ((IOAreg & (IO_1738_USEL | IO_1738_REL)) == (IO_1738_USEL | IO_1738_REL)) | |
return IO_REJECT; | |
if (doDirectorFunc(&dp_dev, TRUE)) { | |
/* | |
* The device interrupt mask has been explicitly changed. If the | |
* device state is such that an interrupt can occur, generate it now. | |
*/ | |
/* | |
* Note: don't check for "Ready and not Busy Interrupt" here since | |
* it's defined as "Next Ready and not Busy", i.e. defer until the | |
* next opportunity. | |
*/ | |
if ((ICHANGED(&DPdev) & IO_DIR_EOP) != 0) { | |
if ((DPdev.STATUS & IO_ST_EOP) != 0) { | |
if ((dp_dev.dctrl & DBG_DINTR) != 0) | |
fprintf(DBGOUT, | |
"%sDP: Mask change EOP interrupt\r\n", INTprefix); | |
RaiseExternalInterrupt(&dp_dev); | |
} | |
} | |
} | |
/* | |
* Handle select/release. | |
*/ | |
if ((IOAreg & (IO_1738_USEL | IO_1738_REL)) != 0) { | |
uint16 unit = (IOAreg & IO_1738_USC) >> 9; | |
if ((dp_dev.flags & DEV_REVERSE) != 0) | |
unit ^= 1; | |
DPdev.STATUS &= ~IO_ST_READY; | |
if ((IOAreg & IO_1738_USEL) != 0) { | |
DPdev.iod_unit = &dp_unit[unit]; | |
if ((dp_unit[unit].flags & UNIT_ATT) != 0) { | |
DPdev.STATUS |= IO_ST_READY; | |
iou = (struct dpio_unit *)dp_unit[unit].up7; | |
if (iou->oncyl) { | |
DPdev.STATUS |= IO_1738_ONCYL; | |
DPdev.ADDRSTATUS = iou->sectorRA; | |
} | |
if ((iou->state == DP_XFER) || (iou->state == DP_SEEK) || DPbusy) | |
DPdev.STATUS |= IO_ST_BUSY; | |
} | |
} | |
if ((IOAreg & IO_1738_REL) != 0) { | |
/*** TODO: check protect conditions ***/ | |
DPdev.iod_unit = NULL; | |
DPdev.STATUS &= ~(IO_1738_ONCYL | IO_ST_BUSY); | |
if (DPbusy) | |
DPdev.STATUS |= IO_ST_BUSY; | |
} | |
} | |
break; | |
/* | |
* Load address | |
*/ | |
case 0x02: | |
if ((uptr = DPdev.iod_unit) != NULL) { | |
iou = (struct dpio_unit *)uptr->up7; | |
if (LoadDiskAddress(uptr, iou, DP_XFER)) { | |
DPdev.STATUS &= IO_ST_READY | IO_ST_PROT; | |
DPdev.STATUS |= IO_ST_BUSY; | |
sim_activate(uptr, DP_XFER_WAIT); | |
} else { | |
if ((dp_dev.dctrl & DBG_DERROR) != 0) | |
fprintf(DBGOUT, | |
"%sDP: Bad Load Address (%04X)\r\n", INTprefix, Areg); | |
fw_IOintr(FALSE, &dp_dev, &DPdev, IO_1738_ADDRERR | IO_ST_EOP | IO_ST_ALARM, 0, 0xFFFF, "Bad load address"); | |
} | |
} else return IO_REJECT; | |
break; | |
/* | |
* Write | |
*/ | |
case 0x03: | |
if ((uptr = DPdev.iod_unit) != NULL) { | |
iou = (struct dpio_unit *)uptr->up7; | |
StartDPDiskIO(uptr, iou, DP_WRITE); | |
} else return IO_REJECT; | |
break; | |
/* | |
* Read | |
*/ | |
case 0x04: | |
if ((uptr = DPdev.iod_unit) != NULL) { | |
iou = (struct dpio_unit *)uptr->up7; | |
StartDPDiskIO(uptr, iou, DP_READ); | |
} else return IO_REJECT; | |
break; | |
/* | |
* Compare | |
*/ | |
case 0x05: | |
if ((uptr = DPdev.iod_unit) != NULL) { | |
iou = (struct dpio_unit *)uptr->up7; | |
StartDPDiskIO(uptr, iou, DP_COMPARE); | |
} else return IO_REJECT; | |
break; | |
/* | |
* Checkword check | |
*/ | |
case 0x06: | |
if ((uptr = DPdev.iod_unit) != NULL) { | |
iou = (struct dpio_unit *)uptr->up7; | |
if (LoadDiskAddress(uptr, iou, DP_CHECKWORD)) { | |
DPdev.STATUS &= IO_ST_READY | IO_ST_PROT | IO_1738_ONCYL; | |
DPdev.STATUS |= IO_ST_BUSY; | |
DPbusy = TRUE; | |
sim_activate(uptr, DP_XFER_WAIT); | |
} else { | |
if ((dp_dev.dctrl & DBG_DERROR) != 0) | |
fprintf(DBGOUT, | |
"%sDP: Bad Checkword Address (%04X)\r\n", | |
INTprefix, Areg); | |
fw_IOintr(FALSE, &dp_dev, &DPdev, IO_1738_ADDRERR | IO_ST_EOP | IO_ST_ALARM, 0, 0xFFFF, "Bad checkword"); | |
} | |
} else return IO_REJECT; | |
break; | |
} | |
return IO_REPLY; | |
} | |
/* | |
* Autoload support | |
*/ | |
t_stat DPautoload(void) | |
{ | |
UNIT *uptr = &dp_unit[(dp_dev.flags & DEV_REVERSE) == 0 ? 0 : 1]; | |
if ((uptr->flags & UNIT_ATT) != 0) { | |
uint32 i; | |
for (i = 0; i < DP_NUMSC; i++) { | |
t_offset offset = i * DP_NUMBY; | |
void *buf = &M[i * DP_NUMWD]; | |
sim_fseeko(uptr->fileref, offset, SEEK_SET); | |
if (sim_fread(buf, sizeof(uint16), DP_NUMWD, uptr->fileref) != DP_NUMWD) | |
return SCPE_IOERR; | |
} | |
return SCPE_OK; | |
} | |
return SCPE_UNATT; | |
} | |
t_stat dp_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) | |
{ | |
const char helpString[] = | |
/****************************************************************************/ | |
" The %D device is a 1738-B disk pack controller.\n" | |
"1 Hardware Description\n" | |
" The 1738-B consists of a controller with up to 2 attached disk drives.\n" | |
" The controller includes a jumper which controls which drive is\n" | |
" addressed as logical disk 0:\n\n" | |
"+sim> SET %D NORMAL\n" | |
"+sim> SET %D REVERSE\n\n" | |
" Each physical drive may be configured as a 853 or 854:\n\n" | |
"+853 drive: 1536000 words per drive\n" | |
"+854 drive: 3118080 words per drive\n\n" | |
" The configuration may be changed by:\n\n" | |
"+sim> SET %U 853\n" | |
"+sim> SET %U 854\n" | |
"2 Equipment Address\n" | |
" Disk controllers are typically set to equipment address 3. This address\n" | |
" may be changed by:\n\n" | |
"+sim> SET %D EQUIPMENT=hexValue\n\n" | |
"2 $Registers\n" | |
"\n" | |
" These registers contain the emulated state of the device. These values\n" | |
" don't necessarily relate to any detail of the original device being\n" | |
" emulated but are merely internal details of the emulation. STATUS always\n" | |
" contains the current status of the device as it would be read by an\n" | |
" application program.\n" | |
"1 Configuration\n" | |
" A %D device is configured with various simh SET and ATTACH commands\n" | |
"2 $Set commands\n"; | |
return scp_help(st, dptr, uptr, flag, helpString, cptr); | |
} |