/* hp2100_ds.c: HP 13037D/13175D disc controller/interface simulator | |
Copyright (c) 2004-2012, Robert M. Supnik | |
Copyright (c) 2012-2017 J. David Bryan | |
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 | |
THE AUTHORS 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 names of the authors shall not be | |
used in advertising or otherwise to promote the sale, use or other dealings | |
in this Software without prior written authorization from the authors. | |
DS 13037D/13175D disc controller/interface | |
11-Jul-17 JDB Renamed "ibl_copy" to "cpu_ibl" | |
15-Mar-17 JDB Trace flags are now global | |
Changed DEBUG_PRI calls to tprintfs | |
10-Mar-17 JDB Added IOBUS to the debug table | |
09-Mar-17 JDB Deprecated LOCKED/WRITEENABLED for PROTECT/UNPROTECT | |
13-May-16 JDB Modified for revised SCP API function parameter types | |
04-Mar-16 JDB Name changed to "hp2100_disclib" until HP 3000 integration | |
30-Dec-14 JDB Added S-register parameters to ibl_copy | |
24-Dec-14 JDB Use T_ADDR_FMT with t_addr values for 64-bit compatibility | |
18-Mar-13 JDB Fixed poll_drives definition to match declaration | |
24-Oct-12 JDB Changed CNTLR_OPCODE to title case to avoid name clash | |
29-Mar-12 JDB Rewritten to use the MAC/ICD disc controller library | |
ioIOO now notifies controller service of parameter output | |
14-Feb-12 JDB Corrected SRQ generation and FIFO under/overrun detection | |
Corrected Clear command to conform to the hardware | |
Fixed Request Status to return Unit Unavailable if illegal | |
Seek and Cold Load Read now Seek Check if seek in progress | |
Remodeled command wait for seek completion | |
10-Feb-12 JDB Deprecated DEVNO in favor of SC | |
21-Jun-11 JDB Corrected status returns for disabled drive, auto-seek | |
beyond drive limits, Request Sector Address and Wakeup | |
with invalid or offline unit | |
Address verification reenabled if auto-seek during | |
Read Without Verify | |
28-Mar-11 JDB Tidied up signal handling | |
26-Oct-10 JDB Changed I/O signal handler for revised signal model | |
26-Jun-08 JDB Rewrote device I/O to model backplane signals | |
31-Dec-07 JDB Corrected and verified ioCRS action | |
20-Dec-07 JDB Corrected DPTR register definition from FLDATA to DRDATA | |
28-Dec-06 JDB Added ioCRS state to I/O decoders | |
03-Aug-06 JDB Fixed REQUEST STATUS command to clear status-1 | |
Removed redundant attached test in "ds_detach" | |
18-Mar-05 RMS Added attached test to detach routine | |
01-Mar-05 JDB Added SET UNLOAD/LOAD | |
References: | |
- 13037 Disc Controller Technical Information Package (13037-90902, Aug-1980) | |
- 7925D Disc Drive Service Manual (07925-90913, Apr-1984) | |
- HP 12992 Loader ROMs Installation Manual (12992-90001, Apr-1986) | |
- DVR32 RTE Moving Head Driver source (92084-18711, Revision 5000) | |
The 13037D multiple-access (MAC) disc controller supports from one to eight | |
HP 7905 (15 MB), 7906 (20MB), 7920 (50 MB), and 7925 (120 MB) disc drives | |
accessed by one to eight CPUs. The controller hardware consists of a 16-bit | |
microprogrammed processor constructed from 74S181 bit slices operating at 5 | |
MHz, a device controller providing the interconnections to the drives and CPU | |
interfaces, and an error correction controller that enables the correction of | |
up to 32-bit error bursts. 1024 words of 24-bit firmware are stored in ROM. | |
The 13175D disc interface is used to connect the HP 1000 CPU to the 13037 | |
device controller. In a multiple-CPU system, one interface is strapped to | |
reset the controller when the CPU's front panel PRESET button is pressed. | |
This module simulates a 13037D connected to a single 13175D interface. From | |
one to eight drives may be connected, and drive types may be freely | |
intermixed. A unit that is enabled but not attached appears to be a | |
connected drive that does not have a disc pack in place. A unit that is | |
disabled appears to be disconnected. | |
This simulator is an adaptation of the code originally written by Bob Supnik. | |
The functions of the controller have been separated from the functions of the | |
interface, with the former placed into a separate disc controller library. | |
This allows the library to support other CPU interfaces, such as the 12821A | |
HP-IB disc interface, that use substantially different communication | |
protocols. The library functions implement the controller command set for | |
the drive units. The interface functions handle the transfer of commands and | |
data to and from the CPU. | |
In hardware, the controller runs continuously in one of three states: in the | |
Poll Loop (idle state), in the Command Wait Loop (wait state), or in command | |
execution (busy state). In simulation, the controller is run only when a | |
command is executing or when a transition into or out of the two loops might | |
occur. Internally, the controller handles these transitions: | |
- when a command other than End terminates (busy => wait) | |
- when the End command terminates (busy => idle) | |
- when a command timeout occurs (wait => idle) | |
- when a parameter timeout occurs (busy => idle) | |
- when a seek completes (if idle and interrupts are enabled, idle => wait) | |
The interface must call the controller library to handle these transitions: | |
- when a command is received from the CPU (idle or wait => busy) | |
- when interrupts are enabled (if idle and drive Attention, idle => wait) | |
In addition, each transition to the wait state must check for a pending | |
command, and each transition to the idle state must check for both a pending | |
command and a drive with Attention status asserted. | |
Implementation notes: | |
1. Although the 13175D has a 16-word FIFO, the "full" level is set at 5 | |
entries in hardware to avoid a long DCPC preemption time at the start of | |
a disc write as the FIFO fills. | |
*/ | |
#include "hp2100_defs.h" | |
#include "hp2100_cpu.h" | |
#include "hp2100_disclib.h" | |
/* Program constants */ | |
#define DS_DRIVES (DL_MAXDRIVE + 1) /* number of disc drive units */ | |
#define DS_UNITS (DS_DRIVES + DL_AUXUNITS) /* total number of units */ | |
#define ds_cntlr ds_unit [DL_MAXDRIVE + 1] /* controller unit alias */ | |
#define FIFO_SIZE 16 /* FIFO depth */ | |
#define FIFO_EMPTY (ds.fifo_count == 0) /* FIFO empty test */ | |
#define FIFO_STOP (ds.fifo_count >= 5) /* FIFO stop filling test */ | |
#define FIFO_FULL (ds.fifo_count == FIFO_SIZE) /* FIFO full test */ | |
#define PRESET_ENABLE TRUE /* Preset Jumper (W4) is enabled */ | |
/* Per-card state variables */ | |
typedef struct { | |
FLIP_FLOP control; /* control flip-flop */ | |
FLIP_FLOP flag; /* flag flip-flop */ | |
FLIP_FLOP flagbuf; /* flag buffer flip-flop */ | |
FLIP_FLOP srq; /* SRQ flip-flop */ | |
FLIP_FLOP edt; /* EDT flip-flop */ | |
FLIP_FLOP cmfol; /* command follows flip-flop */ | |
FLIP_FLOP cmrdy; /* command ready flip-flop */ | |
uint16 fifo [FIFO_SIZE]; /* FIFO buffer */ | |
uint32 fifo_count; /* FIFO occupancy counter */ | |
REG *fifo_reg; /* FIFO register pointer */ | |
} CARD_STATE; | |
/* MAC disc state variables */ | |
static UNIT ds_unit [DS_UNITS]; /* unit array */ | |
static CARD_STATE ds; /* card state */ | |
static uint16 buffer [DL_BUFSIZE]; /* command/status/sector buffer */ | |
static CNTLR_VARS mac_cntlr = /* MAC controller */ | |
{ CNTLR_INIT (MAC, buffer, &ds_cntlr) }; | |
/* MAC disc global VM routines */ | |
IOHANDLER ds_io; | |
t_stat ds_service_drive (UNIT *uptr); | |
t_stat ds_service_controller (UNIT *uptr); | |
t_stat ds_service_timer (UNIT *uptr); | |
t_stat ds_reset (DEVICE *dptr); | |
t_stat ds_attach (UNIT *uptr, CONST char *cptr); | |
t_stat ds_detach (UNIT *uptr); | |
t_stat ds_boot (int32 unitno, DEVICE *dptr); | |
/* MAC disc global SCP routines */ | |
t_stat ds_load_unload (UNIT *uptr, int32 value, CONST char *cptr, void *desc); | |
/* MAC disc local utility routines */ | |
static void start_command (void); | |
static void poll_interface (void); | |
static void poll_drives (void); | |
static void fifo_load (uint16 data); | |
static uint16 fifo_unload (void); | |
static void fifo_clear (void); | |
static t_stat activate_unit (UNIT *uptr); | |
/* MAC disc VM data structures. | |
ds_dib DS device information block | |
ds_unit DS unit list | |
ds_reg DS register list | |
ds_mod DS modifier list | |
ds_deb DS debug table | |
ds_dev DS device descriptor | |
For the drive models, the modifiers provide this SHOW behavior: | |
- when detached and autosized, prints "autosize" | |
- when detached and not autosized, prints the model number | |
- when attached, prints the model number (regardless of autosizing) | |
Implementation notes: | |
1. The validation routine does not allow the model number or autosizing | |
option to be changed when the unit is attached. Therefore, specifying | |
UNIT_ATT in the mask field has no adverse effect. | |
2. The modifier DEVNO is deprecated in favor of SC but is retained for | |
compatibility. | |
*/ | |
DEVICE ds_dev; | |
static DIB ds_dib = { &ds_io, DS }; | |
#define UNIT_FLAGS (UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | UNIT_DISABLE | UNIT_UNLOAD) | |
static UNIT ds_unit [] = { | |
{ UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 0 */ | |
{ UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 1 */ | |
{ UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 2 */ | |
{ UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 3 */ | |
{ UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 4 */ | |
{ UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 5 */ | |
{ UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 6 */ | |
{ UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 7 */ | |
{ UDATA (&ds_service_controller, UNIT_DIS, 0) }, /* controller unit */ | |
{ UDATA (&ds_service_timer, UNIT_DIS, 0) } /* timer unit */ | |
}; | |
static REG ds_reg [] = { | |
{ FLDATA (CMFOL, ds.cmfol, 0) }, | |
{ FLDATA (CMRDY, ds.cmrdy, 0) }, | |
{ DRDATA (FCNT, ds.fifo_count, 5) }, | |
{ BRDATA (FIFO, ds.fifo, 8, 16, FIFO_SIZE), REG_CIRC }, | |
{ ORDATA (FREG, ds.fifo_reg, 32), REG_HRO }, | |
{ ORDATA (CNTYPE, mac_cntlr.type, 2), REG_HRO }, | |
{ ORDATA (STATE, mac_cntlr.state, 2) }, | |
{ ORDATA (OPCODE, mac_cntlr.opcode, 6) }, | |
{ ORDATA (STATUS, mac_cntlr.status, 6) }, | |
{ FLDATA (EOC, mac_cntlr.eoc, 0) }, | |
{ FLDATA (EOD, mac_cntlr.eod, 0) }, | |
{ ORDATA (SPDU, mac_cntlr.spd_unit, 16) }, | |
{ ORDATA (FLMASK, mac_cntlr.file_mask, 4) }, | |
{ ORDATA (RETRY, mac_cntlr.retry, 4), REG_HRO }, | |
{ ORDATA (CYL, mac_cntlr.cylinder, 16) }, | |
{ ORDATA (HEAD, mac_cntlr.head, 6) }, | |
{ ORDATA (SECTOR, mac_cntlr.sector, 8) }, | |
{ ORDATA (VFYCNT, mac_cntlr.verify_count, 16) }, | |
{ ORDATA (LASPOL, mac_cntlr.poll_unit, 3) }, | |
{ HRDATA (BUFPTR, mac_cntlr.buffer, 32), REG_HRO }, | |
{ BRDATA (BUFFER, buffer, 8, 16, DL_BUFSIZE) }, | |
{ DRDATA (INDEX, mac_cntlr.index, 8) }, | |
{ DRDATA (LENGTH, mac_cntlr.length, 8) }, | |
{ HRDATA (AUXPTR, mac_cntlr.aux, 32), REG_HRO }, | |
{ DRDATA (STIME, mac_cntlr.seek_time, 24), PV_LEFT | REG_NZ }, | |
{ DRDATA (ITIME, mac_cntlr.sector_time, 24), PV_LEFT | REG_NZ }, | |
{ DRDATA (CTIME, mac_cntlr.cmd_time, 24), PV_LEFT | REG_NZ }, | |
{ DRDATA (DTIME, mac_cntlr.data_time, 24), PV_LEFT | REG_NZ }, | |
{ DRDATA (WTIME, mac_cntlr.wait_time, 31), PV_LEFT | REG_NZ }, | |
{ FLDATA (CTL, ds.control, 0) }, | |
{ FLDATA (FLG, ds.flag, 0) }, | |
{ FLDATA (FBF, ds.flagbuf, 0) }, | |
{ FLDATA (SRQ, ds.srq, 0) }, | |
{ FLDATA (EDT, ds.edt, 0) }, | |
{ URDATA (UCYL, ds_unit[0].CYL, 10, 10, 0, DS_UNITS, PV_LEFT) }, | |
{ URDATA (UOP, ds_unit[0].OP, 8, 6, 0, DS_UNITS, PV_RZRO) }, | |
{ URDATA (USTAT, ds_unit[0].STAT, 2, 8, 0, DS_UNITS, PV_RZRO) }, | |
{ URDATA (UPHASE, ds_unit[0].PHASE, 8, 3, 0, DS_UNITS, PV_RZRO) }, | |
{ URDATA (UPOS, ds_unit[0].pos, 8, T_ADDR_W, 0, DS_UNITS, PV_LEFT) }, | |
{ URDATA (UWAIT, ds_unit[0].wait, 8, 32, 0, DS_UNITS, PV_LEFT) }, | |
{ ORDATA (SC, ds_dib.select_code, 6), REG_HRO }, | |
{ ORDATA (DEVNO, ds_dib.select_code, 6), REG_HRO }, | |
{ NULL } | |
}; | |
static MTAB ds_mod [] = { | |
/* Mask Value Match Value Print String Match String Validation Display Descriptor */ | |
/* ------------ ------------ ----------------- --------------- ---------------- ------- ---------- */ | |
{ UNIT_UNLOAD, UNIT_UNLOAD, "heads unloaded", "UNLOADED", &ds_load_unload, NULL, NULL }, | |
{ UNIT_UNLOAD, 0, "heads loaded", "LOADED", &ds_load_unload, NULL, NULL }, | |
{ UNIT_WLK, UNIT_WLK, "protected", "PROTECT", NULL, NULL, NULL }, | |
{ UNIT_WLK, 0, "unprotected", "UNPROTECT", NULL, NULL, NULL }, | |
{ UNIT_WLK, UNIT_WLK, NULL, "LOCKED", NULL, NULL, NULL }, | |
{ UNIT_WLK, 0, NULL, "WRITEENABLED", NULL, NULL, NULL }, | |
{ UNIT_FMT, UNIT_FMT, "format enabled", "FORMAT", NULL, NULL, NULL }, | |
{ UNIT_FMT, 0, "format disabled", "NOFORMAT", NULL, NULL, NULL }, | |
/* Print Match */ | |
/* Mask Value Match Value String String Validation Disp Desc */ | |
/* --------------------------------- --------------------- ---------- ---------- ------------- ---- ---- */ | |
{ UNIT_AUTO | UNIT_ATT, UNIT_AUTO, "autosize", "AUTOSIZE", &dl_set_model, NULL, NULL }, | |
{ UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7905, "7905", "7905", &dl_set_model, NULL, NULL }, | |
{ UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7906, "7906", "7906", &dl_set_model, NULL, NULL }, | |
{ UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7920, "7920", "7920", &dl_set_model, NULL, NULL }, | |
{ UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7925, "7925", "7925", &dl_set_model, NULL, NULL }, | |
{ UNIT_ATT | UNIT_MODEL, UNIT_ATT | MODEL_7905, "7905", NULL, NULL, NULL, NULL }, | |
{ UNIT_ATT | UNIT_MODEL, UNIT_ATT | MODEL_7906, "7906", NULL, NULL, NULL, NULL }, | |
{ UNIT_ATT | UNIT_MODEL, UNIT_ATT | MODEL_7920, "7920", NULL, NULL, NULL, NULL }, | |
{ UNIT_ATT | UNIT_MODEL, UNIT_ATT | MODEL_7925, "7925", NULL, NULL, NULL, NULL }, | |
/* Entry Flags Value Print String Match String Validation Display Descriptor */ | |
/* ------------------- ----- ------------ ------------ ------------ ------------- ---------------- */ | |
{ MTAB_XDV, 1u, "SC", "SC", &hp_set_dib, &hp_show_dib, (void *) &ds_dib }, | |
{ MTAB_XDV | MTAB_NMO, ~1u, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &ds_dib }, | |
{ 0 } | |
}; | |
static DEBTAB ds_deb [] = { | |
{ "RWSC", DEB_RWSC }, | |
{ "CMDS", DEB_CMDS }, | |
{ "CPU", DEB_CPU }, | |
{ "BUF", DEB_BUF }, | |
{ "SERV", DEB_SERV }, | |
{ "IOBUS", TRACE_IOBUS }, | |
{ NULL, 0 } | |
}; | |
DEVICE ds_dev = { | |
"DS", /* device name */ | |
ds_unit, /* unit array */ | |
ds_reg, /* register array */ | |
ds_mod, /* modifier array */ | |
DS_UNITS, /* number of units */ | |
8, /* address radix */ | |
27, /* address width = 128 MB */ | |
1, /* address increment */ | |
8, /* data radix */ | |
16, /* data width */ | |
NULL, /* examine routine */ | |
NULL, /* deposit routine */ | |
&ds_reset, /* reset routine */ | |
&ds_boot, /* boot routine */ | |
&ds_attach, /* attach routine */ | |
&ds_detach, /* detach routine */ | |
&ds_dib, /* device information block */ | |
DEV_DEBUG | DEV_DISABLE, /* device flags */ | |
0, /* debug control flags */ | |
ds_deb, /* debug flag name table */ | |
NULL, /* memory size change routine */ | |
NULL /* logical device name */ | |
}; | |
/* MAC disc global VM routines */ | |
/* I/O signal handler. | |
The 13175D disc interface data path consists of an input multiplexer/latch | |
and a 16-word FIFO buffer. The FIFO source may be either the CPU's I/O | |
input bus or the controller's interface data bus. The output of the FIFO may | |
be enabled either to the CPU's I/O output bus or the interface data bus. | |
The control path consists of the usual control, flag buffer, flag, and SRQ | |
flip-flops, although flag and SRQ are decoupled to allow the full DCPC | |
transfer rate through the FIFO (driving SRQ from the flag limits transfers to | |
every other cycle). SRQ is based on the FIFO level: if data or room in the | |
FIFO is available, SRQ is set to initiate a transfer. The flag is only used | |
to signal an interrupt at the end of a command. | |
One unusual aspect is that SFC and SFS test different things, rather than | |
complementary states of the same thing. SFC tests the controller busy state, | |
and SFS tests the flag flip-flop. | |
In addition, the card contains end-of-data-transfer, command-follows, and | |
command-ready flip-flops. EDT is set when the DCPC EDT signal is asserted | |
and is used in conjunction with the FIFO level to assert the end-of-data | |
signal to the controller. The command-follows flip-flop is set by a CLC to | |
indicate that the next data word output from the CPU is a disc command. The | |
command-ready flip-flop is set when a command is received to schedule an | |
interface poll. | |
Implementation notes: | |
1. In hardware, SRQ is enabled only when the controller is reading or | |
writing the disc (IFIN or IFOUT functions are asserted) and set when the | |
FIFO is not empty (read) or not full (write). In simulation, SRQ is set | |
by the unit service read/write data phase transfers and cleared in the | |
IOI and IOO signal handlers when the FIFO is empty (read) or full | |
(write). | |
2. The DCPC EDT signal cannot set the controller's end-of-data flag directly | |
because a write EOD must occur only after the FIFO has been drained. | |
3. Polling the interface or drives must be deferred to the end of I/O signal | |
handling. If they are performed in the IOO/STC handlers themselves, an | |
associated CLF might clear the flag that was set by the poll. | |
4. Executing a CLC sets the controller's end-of-data flag, which will abort | |
a read or write data transfer in progress. Parameter transfers are not | |
affected. If a command is received when a parameter is expected, the | |
word is interpreted as data, even though the command-ready flip-flop is | |
set. The controller firmware only checks DTRDY for a parameter transfer, | |
and DTRDY is asserted whenever the FIFO is not empty. | |
5. The hardware Interface Function and Flag Buses are not implemented | |
explicitly. Instead, interface functions and signals are inferred by the | |
interface from the current command operation and phase. | |
*/ | |
uint32 ds_io (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) | |
{ | |
static const char * const output_state [] = { "Data", "Command" }; | |
const char * const hold_or_clear = (signal_set & ioCLF ? ",C" : ""); | |
uint16 data; | |
t_stat status; | |
IOSIGNAL signal; | |
IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ | |
t_bool command_issued = FALSE; | |
t_bool interrupt_enabled = FALSE; | |
while (working_set) { | |
signal = IONEXT (working_set); /* isolate the next signal */ | |
switch (signal) { /* dispatch the I/O signal */ | |
case ioCLF: /* clear flag flip-flop */ | |
ds.flag = CLEAR; /* clear the flag */ | |
ds.flagbuf = CLEAR; /* and flag buffer */ | |
tprintf (ds_dev, DEB_CMDS, "[CLF] Flag cleared\n"); | |
break; | |
case ioSTF: /* set flag flip-flop */ | |
case ioENF: /* enable flag */ | |
ds.flag = SET; /* set the flag */ | |
ds.flagbuf = SET; /* and flag buffer */ | |
tprintf (ds_dev, DEB_CMDS, "[STF] Flag set\n"); | |
break; | |
case ioSFC: /* skip if flag is clear */ | |
setSKF (mac_cntlr.state != cntlr_busy); /* skip if the controller is not busy */ | |
break; | |
case ioSFS: /* skip if flag is set */ | |
setstdSKF (ds); /* assert SKF if the flag is set */ | |
break; | |
case ioIOI: /* I/O data input */ | |
data = fifo_unload (); /* unload the next word from the FIFO */ | |
stat_data = IORETURN (SCPE_OK, data); /* merge in the return status */ | |
tprintf (ds_dev, DEB_CPU, "[LIx%s] Data = %06o\n", hold_or_clear, data); | |
if (FIFO_EMPTY) { /* is the FIFO now empty? */ | |
if (ds.srq == SET) | |
tprintf (ds_dev, DEB_CMDS, "[LIx%s] SRQ cleared\n", hold_or_clear); | |
ds.srq = CLEAR; /* clear SRQ */ | |
if (ds_cntlr.PHASE == data_phase) { /* is this an outbound parameter? */ | |
ds_cntlr.wait = mac_cntlr.data_time; /* activate the controller */ | |
activate_unit (&ds_cntlr); /* to acknowledge the data */ | |
} | |
} | |
break; | |
case ioIOO: /* I/O data output */ | |
data = IODATA (stat_data); /* mask to just the data word */ | |
tprintf (ds_dev, DEB_CPU, "[OTx%s] %s = %06o\n", | |
hold_or_clear, output_state [ds.cmfol], data); | |
fifo_load (data); /* load the word into the FIFO */ | |
if (ds.cmfol == SET) { /* are we expecting a command? */ | |
ds.cmfol = CLEAR; /* clear the command follows flip-flop */ | |
ds.cmrdy = SET; /* set the command ready flip-flop */ | |
command_issued = TRUE; /* and request an interface poll */ | |
} | |
else { /* not a command */ | |
if (ds_cntlr.PHASE == data_phase) { /* is this an inbound parameter? */ | |
ds_cntlr.wait = mac_cntlr.data_time; /* activate the controller */ | |
activate_unit (&ds_cntlr); /* to receive the data */ | |
} | |
if (FIFO_STOP) { /* is the FIFO now full enough? */ | |
if (ds.srq == SET) | |
tprintf (ds_dev, DEB_CMDS, "[OTx%s] SRQ cleared\n", hold_or_clear); | |
ds.srq = CLEAR; /* clear SRQ to stop filling */ | |
} | |
} | |
break; | |
case ioPOPIO: /* power-on preset to I/O */ | |
ds.flag = SET; /* set the flag */ | |
ds.flagbuf = SET; /* and flag buffer */ | |
ds.cmrdy = CLEAR; /* clear the command ready flip-flop */ | |
tprintf (ds_dev, DEB_CMDS, "[POPIO] Flag set\n"); | |
break; | |
case ioCRS: /* control reset */ | |
tprintf (ds_dev, DEB_CMDS, "[CRS] Master reset\n"); | |
ds.control = CLEAR; /* clear the control */ | |
ds.cmfol = CLEAR; /* and command follows flip-flops */ | |
if (PRESET_ENABLE) { /* is preset enabled for this interface? */ | |
fifo_clear (); /* clear the FIFO */ | |
status = dl_clear_controller (&mac_cntlr, /* do a hard clear of the controller */ | |
ds_unit, hard_clear); | |
stat_data = IORETURN (status, 0); /* return the status from the controller */ | |
} | |
break; | |
case ioCLC: /* clear control flip-flop */ | |
tprintf (ds_dev, DEB_CMDS, "[CLC%s] Control cleared\n", hold_or_clear); | |
ds.control = CLEAR; /* clear the control */ | |
ds.edt = CLEAR; /* and EDT flip-flops */ | |
ds.cmfol = SET; /* set the command follows flip-flop */ | |
mac_cntlr.eod = SET; /* set the controller's EOD flag */ | |
fifo_clear (); /* clear the FIFO */ | |
break; | |
case ioSTC: /* set control flip-flop */ | |
ds.control = SET; /* set the control flip-flop */ | |
interrupt_enabled = TRUE; /* check for drive attention */ | |
tprintf (ds_dev, DEB_CMDS, "[STC%s] Control set\n", hold_or_clear); | |
break; | |
case ioEDT: /* end data transfer */ | |
ds.edt = SET; /* set the EDT flip-flop */ | |
tprintf (ds_dev, DEB_CPU, "[EDT] DCPC transfer ended\n"); | |
break; | |
case ioSIR: /* set interrupt request */ | |
setstdPRL (ds); /* set the standard PRL signal */ | |
setstdIRQ (ds); /* set the standard IRQ signal */ | |
setSRQ (dibptr->select_code, ds.srq); /* set the SRQ signal */ | |
break; | |
case ioIAK: /* interrupt acknowledge */ | |
ds.flagbuf = CLEAR; /* clear the flag */ | |
break; | |
default: /* all other signals */ | |
break; /* are ignored */ | |
} | |
working_set = working_set & ~signal; /* remove the current signal from the set */ | |
} | |
if (command_issued) /* was a command received? */ | |
poll_interface (); /* poll the interface for the next command */ | |
else if (interrupt_enabled) /* were interrupts enabled? */ | |
poll_drives (); /* poll the drives for Attention */ | |
return stat_data; | |
} | |
/* Service the disc drive unit. | |
The unit service routine is called to execute scheduled controller commands | |
for the specified unit. The actions to be taken depend on the current state | |
of the controller and the unit. | |
Generally, the controller library service routine handles all of the disc | |
operations except data transfer to and from the interface. Read transfers | |
are responsible for loading words from the sector buffer into the FIFO and | |
enabling SRQ. If the current sector transfer is complete, either due to EDT | |
assertion or buffer exhaustion, the controller is moved to the end phase to | |
complete or continue the read with the next sector. In either case, the unit | |
is rescheduled. If the FIFO overflows, the read terminates with a data | |
overrun error. | |
Write transfers set the initial SRQ to request words from the CPU. As each | |
word arrives, it is unloaded from the FIFO into the sector buffer, and SRQ is | |
enabled. If the current sector transfer is complete, the controller is moved | |
to the end phase. If the FIFO underflows, the write terminates with a data | |
overrun error. | |
The synchronous nature of the disc drive requires that data be supplied or | |
accepted continuously by the CPU. DCPC generally assures that this occurs, | |
and the FIFO allows for some latency before an overrun or underrun occurs. | |
The other operation the interface must handle is seek completion. The | |
controller handles seek completion by setting Attention status in the drive's | |
status word. The interface is responsible for polling the drives if the | |
controller is idle and interrupts are enabled. | |
Implementation notes: | |
1. Every command except Seek, Recalibrate, and End sets the flag when the | |
command completes. A command completes when the controller is no longer | |
busy (it becomes idle for Seek, Recalibrate, and End, or it becomes | |
waiting for all others). Seek and Recalibrate may generate errors (e.g., | |
heads unloaded), in which case the flag must be set. But in these cases, | |
the controller state is waiting, not idle. | |
However, it is insufficient simply to check that the controller has moved | |
to the wait state, because a seek may complete while the controller is | |
waiting for the next command. For example, a Seek is started on unit 0, | |
and the controller moves to the idle state. But before the seek | |
completes, another command is issued that attempts to access unit 1, | |
which is not ready. The command fails with a Status-2 error, and the | |
controller moves to the wait state. When the seek completes, the | |
controller is waiting with error status. We must determine whether the | |
seek completed successfully or not, as we must interrupt in the latter | |
case. | |
Therefore, we determine seek completion by checking if the Attention | |
status was set. Attention sets only if the seek completes successfully. | |
(Actually, Attention sets if a seek check occurs, but in that case, the | |
command terminated before the seek ever started. Also, a seek may | |
complete while the controller is busy, waiting, or idle.) | |
2. For debug printouts, we want to print the name of the command that has | |
completed when the controller returns to the idle or wait state. | |
Normally, we would use the controller's "opcode" field to identify the | |
command that completed. However, while waiting for Seek or Recalibrate | |
completion, "opcode" may be set to another command if that command does | |
not access this drive. For example, it might be set to a Read of another | |
unit, or a Request Status for this unit. So we can't rely on "opcode" to | |
report the correct name of the completed positioning command. | |
However, we cannot rely on "uptr->OP" either, as that can be changed | |
during the course of a command. For example, Read Without Verify is | |
changed to Read after a track crossing. | |
Instead, we have to determine whether a seek is completing. If it is, | |
then we report "uptr->OP"; otherwise, we report "opcode". | |
3. The initial write SRQ must set only at the transition from the start | |
phase to the data phase. If a write command begins with an auto-seek, | |
the drive service will be entered twice in the start phase (the first | |
entry performs the seek, and the second begins the write). In hardware, | |
SRQ does not assert until the write begins. | |
4. The DCPC EDT signal cannot set the controller's end-of-data flag | |
directly because a write EOD must only occur after the FIFO has been | |
drained. | |
*/ | |
t_stat ds_service_drive (UNIT *uptr) | |
{ | |
t_stat result; | |
t_bool seek_completion; | |
FLIP_FLOP entry_srq = ds.srq; /* get the SRQ state on entry */ | |
CNTLR_PHASE entry_phase = (CNTLR_PHASE) uptr->PHASE; /* get the operation phase on entry */ | |
uint32 entry_status = uptr->STAT; /* get the drive status on entry */ | |
result = dl_service_drive (&mac_cntlr, uptr); /* service the drive */ | |
if ((CNTLR_PHASE) uptr->PHASE == data_phase) /* is the drive in the data phase? */ | |
switch ((CNTLR_OPCODE) uptr->OP) { /* dispatch the current operation */ | |
case Read: /* read operations */ | |
case Read_Full_Sector: | |
case Read_With_Offset: | |
case Read_Without_Verify: | |
if (mac_cntlr.length == 0 || ds.edt == SET) { /* is the data phase complete? */ | |
mac_cntlr.eod = ds.edt; /* set EOD if DCPC is done */ | |
uptr->PHASE = end_phase; /* set the end phase */ | |
uptr->wait = mac_cntlr.cmd_time; /* and schedule the controller */ | |
} | |
else if (FIFO_FULL) /* is the FIFO already full? */ | |
dl_end_command (&mac_cntlr, data_overrun); /* terminate the command with an overrun */ | |
else { | |
fifo_load (buffer [mac_cntlr.index++]); /* load the next word into the FIFO */ | |
mac_cntlr.length--; /* count it */ | |
ds.srq = SET; /* ask DCPC to pick it up */ | |
ds_io (&ds_dib, ioSIR, 0); /* and recalculate the interrupts */ | |
uptr->wait = mac_cntlr.data_time; /* schedule the next data transfer */ | |
} | |
break; | |
case Write: /* write operations */ | |
case Write_Full_Sector: | |
case Initialize: | |
if (entry_phase == start_phase) { /* is this the phase transition? */ | |
ds.srq = SET; /* start the DCPC transfer */ | |
ds_io (&ds_dib, ioSIR, 0); /* and recalculate the interrupts */ | |
} | |
else if (FIFO_EMPTY) /* is the FIFO empty? */ | |
dl_end_command (&mac_cntlr, data_overrun); /* terminate the command with an underrun */ | |
else { | |
buffer [mac_cntlr.index++] = fifo_unload (); /* unload the next word from the FIFO */ | |
mac_cntlr.length--; /* count it */ | |
if (ds.edt == SET && FIFO_EMPTY) /* if DCPC is complete and the FIFO is empty */ | |
mac_cntlr.eod = SET; /* then set the end-of-data flag */ | |
if (mac_cntlr.length == 0 || mac_cntlr.eod == SET) { /* is the data phase complete? */ | |
uptr->PHASE = end_phase; /* set the end phase */ | |
uptr->wait = mac_cntlr.cmd_time; /* and schedule the controller */ | |
} | |
else { | |
if (ds.edt == CLEAR) { /* if DCPC is still transferring */ | |
ds.srq = SET; /* then request the next word */ | |
ds_io (&ds_dib, ioSIR, 0); /* and recalculate the interrupts */ | |
} | |
uptr->wait = mac_cntlr.data_time; /* schedule the next data transfer */ | |
} | |
} | |
break; | |
default: /* we were entered with an invalid state */ | |
result = SCPE_IERR; /* return an internal (programming) error */ | |
break; | |
} /* end of data phase operation dispatch */ | |
if (entry_srq != ds.srq) | |
tprintf (ds_dev, DEB_CMDS, "SRQ %s\n", ds.srq == SET ? "set" : "cleared"); | |
if (uptr->wait) /* was service requested? */ | |
activate_unit (uptr); /* schedule the next event */ | |
seek_completion = ~entry_status & uptr->STAT & DL_S2ATN; /* seek is complete when Attention sets */ | |
if (mac_cntlr.state != cntlr_busy) { /* is the command complete? */ | |
if (mac_cntlr.state == cntlr_wait && !seek_completion) /* is it command but not seek completion? */ | |
ds_io (&ds_dib, ioENF, 0); /* set the data flag to interrupt the CPU */ | |
poll_interface (); /* poll the interface for the next command */ | |
poll_drives (); /* poll the drives for Attention */ | |
} | |
if (result == SCPE_IERR) /* did an internal error occur? */ | |
tprintf (ds_dev, DEB_RWSC, "Unit %d %s command %s phase service not handled\n", | |
uptr - ds_unit, dl_opcode_name (MAC, (CNTLR_OPCODE) uptr->OP), | |
dl_phase_name ((CNTLR_PHASE) uptr->PHASE)); | |
else if (seek_completion) /* if a seek has completed */ | |
tprintf (ds_dev, DEB_RWSC, "Unit %d %s command completed\n", /* report the unit command */ | |
uptr - ds_unit, dl_opcode_name (MAC, (CNTLR_OPCODE) uptr->OP)); | |
else if (mac_cntlr.state == cntlr_wait) /* if the controller has stopped */ | |
tprintf (ds_dev, DEB_RWSC, "Unit %d %s command completed\n", /* report the controller command */ | |
uptr - ds_unit, dl_opcode_name (MAC, mac_cntlr.opcode)); | |
return result; /* return the result of the service */ | |
} | |
/* Service the controller unit. | |
The controller service routine is called to execute scheduled controller | |
commands that do not access drive units. It is also called to obtain command | |
parameters from the interface and to return command result values to the | |
interface. | |
Most controller commands are handled completely in the library's service | |
routine, so we call that first. Commands that neither accept nor supply | |
parameters are complete when the library routine returns, so all we have to | |
do is set the interface flag if required. | |
For parameter transfers in the data phase, the interface is responsible for | |
moving words between the sector buffer and the FIFO and setting the flag to | |
notify the CPU. | |
Implementation notes: | |
1. In hardware, the Read With Offset command sets the data flag after the | |
offset parameter has been read and the head positioner has been moved by | |
the indicated amount. The intent is to delay the DCPC start until the | |
drive is ready to supply data from the disc. | |
In simulation, the flag is set as soon as the parameter is received. | |
*/ | |
t_stat ds_service_controller (UNIT *uptr) | |
{ | |
t_stat result; | |
const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OP; | |
result = dl_service_controller (&mac_cntlr, uptr); /* service the controller */ | |
switch ((CNTLR_PHASE) uptr->PHASE) { /* dispatch the current phase */ | |
case start_phase: /* most controller operations */ | |
case end_phase: /* start and end on the same phase */ | |
switch (opcode) { /* dispatch the current operation */ | |
case Request_Status: | |
case Request_Sector_Address: | |
case Address_Record: | |
case Request_Syndrome: | |
case Load_TIO_Register: | |
case Request_Disc_Address: | |
case End: | |
break; /* complete the operation without setting the flag */ | |
case Clear: | |
case Set_File_Mask: | |
case Wakeup: | |
ds_io (&ds_dib, ioENF, 0); /* complete the operation and set the flag */ | |
break; | |
default: /* we were entered with an invalid state */ | |
result = SCPE_IERR; /* return an internal (programming) error */ | |
break; | |
} /* end of operation dispatch */ | |
break; /* end of start and end phase handlers */ | |
case data_phase: | |
switch (opcode) { /* dispatch the current operation */ | |
case Seek: /* operations that accept parameters */ | |
case Verify: | |
case Address_Record: | |
case Read_With_Offset: | |
case Load_TIO_Register: | |
buffer [mac_cntlr.index++] = fifo_unload (); /* unload the next word from the FIFO */ | |
mac_cntlr.length--; /* count it */ | |
if (mac_cntlr.length) /* are there more words to transfer? */ | |
ds_io (&ds_dib, ioENF, 0); /* set the flag to request the next one */ | |
else { /* all parameters have been received */ | |
uptr->PHASE = end_phase; /* set the end phase */ | |
if (opcode == Read_With_Offset) /* a Read With Offset command sets the flag */ | |
ds_io (&ds_dib, ioENF, 0); /* to indicate that offsetting is complete */ | |
start_command (); /* the command is now ready to execute */ | |
} | |
break; | |
case Request_Status: /* operations that supply parameters */ | |
case Request_Sector_Address: | |
case Request_Syndrome: | |
case Request_Disc_Address: | |
if (mac_cntlr.length) { /* are there more words to return? */ | |
fifo_load (buffer [mac_cntlr.index++]); /* load the next word into the FIFO */ | |
mac_cntlr.length--; /* count it */ | |
ds_io (&ds_dib, ioENF, 0); /* set the flag to request pickup by the CPU */ | |
} | |
else { /* all parameters have been sent */ | |
uptr->PHASE = end_phase; /* set the end phase */ | |
uptr->wait = mac_cntlr.cmd_time; /* schedule the controller */ | |
activate_unit (uptr); /* to complete the command */ | |
} | |
break; | |
default: /* we were entered with an invalid state */ | |
result = SCPE_IERR; /* return an internal (programming) error */ | |
break; | |
} /* end of operation dispatch */ | |
break; /* end of data phase handlers */ | |
} /* end of phase dispatch */ | |
if (result == SCPE_IERR) /* did an internal error occur? */ | |
tprintf (ds_dev, DEB_RWSC, "Controller %s command %s phase service not handled\n", | |
dl_opcode_name (MAC, opcode), dl_phase_name ((CNTLR_PHASE) uptr->PHASE)); | |
if (mac_cntlr.state != cntlr_busy) { /* has the controller stopped? */ | |
poll_interface (); /* poll the interface for the next command */ | |
poll_drives (); /* poll the drives for Attention status */ | |
tprintf (ds_dev, DEB_RWSC, "Controller %s command completed\n", | |
dl_opcode_name (MAC, opcode)); | |
} | |
return result; /* return the result of the service */ | |
} | |
/* Service the command wait timer unit. | |
The command wait timer service routine is called if the command wait timer | |
expires. The library is called to reset the file mask and idle the | |
controller. Then the interface is polled for a command and the drives are | |
polled for Attention status. | |
*/ | |
t_stat ds_service_timer (UNIT *uptr) | |
{ | |
t_stat result; | |
result = dl_service_timer (&mac_cntlr, uptr); /* service the timer */ | |
poll_interface (); /* poll the interface for the next command */ | |
poll_drives (); /* poll the drives for Attention status */ | |
return result; /* return the result of the service */ | |
} | |
/* Reset the simulator. | |
In hardware, the PON signal clears the Interface Selected flip-flop, | |
disconnecting the interface from the disc controller. In simulation, the | |
interface always remains connected to the controller, so no special action is | |
needed. | |
Implementation notes: | |
1. During a power-on reset, a pointer to the FIFO simulation register is | |
saved to allow access to the "qptr" field during FIFO loading and | |
unloading. This enables SCP to view the FIFO as a circular queue, so | |
that the bottom word of the FIFO is always displayed as FIFO[0], | |
regardless of where it is in the actual FIFO array. | |
2. SRQ is denied because neither IFIN nor IFOUT is asserted when the | |
interface is not selected. | |
*/ | |
t_stat ds_reset (DEVICE *dptr) | |
{ | |
uint32 unit; | |
if (sim_switches & SWMASK ('P')) { /* is this a power-on reset? */ | |
ds.fifo_reg = find_reg ("FIFO", NULL, dptr); /* find the FIFO register entry */ | |
if (ds.fifo_reg == NULL) /* if it cannot be found, */ | |
return SCPE_IERR; /* report a programming error */ | |
else { /* found it */ | |
ds.fifo_reg->qptr = 0; /* so reset the FIFO bottom index */ | |
ds.fifo_count = 0; /* and clear the FIFO */ | |
} | |
for (unit = 0; unit < dptr->numunits; unit++) { /* loop through all of the units */ | |
sim_cancel (dptr->units + unit); /* cancel activation */ | |
dptr->units [unit].CYL = 0; /* reset the head position to cylinder 0 */ | |
dptr->units [unit].pos = 0; /* (irrelevant for the controller and timer) */ | |
} | |
} | |
IOPRESET (&ds_dib); /* PRESET the device */ | |
ds.srq = CLEAR; /* clear SRQ */ | |
return SCPE_OK; | |
} | |
/* Attach a drive unit. | |
The specified file is attached to the indicated drive unit. The library | |
attach routine will load the heads. This will set the First Status and | |
Attention bits in the drive status, so we poll the drives to ensure that the | |
CPU is notified that the drive is now online. | |
Implementation notes: | |
1. If we are called during a RESTORE command, the drive status will not be | |
changed, so polling the drives will have no effect. | |
*/ | |
t_stat ds_attach (UNIT *uptr, CONST char *cptr) | |
{ | |
t_stat result; | |
result = dl_attach (&mac_cntlr, uptr, cptr); /* attach the drive */ | |
if (result == SCPE_OK) /* was the attach successful? */ | |
poll_drives (); /* poll the drives to notify the CPU */ | |
return result; | |
} | |
/* Detach a drive unit. | |
The specified file is detached from the indicated drive unit. The library | |
detach routine will unload the heads. This will set the Attention bit in the | |
drive status, so we poll the drives to ensure that the CPU is notified that | |
the drive is now offline. | |
*/ | |
t_stat ds_detach (UNIT *uptr) | |
{ | |
t_stat result; | |
result = dl_detach (&mac_cntlr, uptr); /* detach the drive */ | |
if (result == SCPE_OK) /* was the detach successful? */ | |
poll_drives (); /* poll the drives to notify the CPU */ | |
return result; | |
} | |
/* Boot a MAC disc drive. | |
The MAC disc bootstrap program is loaded from the HP 12992B Boot Loader ROM | |
into memory, the I/O instructions are configured for the interface card's | |
select code, and the program is run to boot from the specified unit. The | |
loader supports booting from cylinder 0 of drive unit 0 only. Before | |
execution, the S register is automatically set as follows: | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
------ ------ ---------------------- --------- --------- | |
ROM # 0 1 select code reserved head | |
The boot routine sets bits 15-6 of the S register to appropriate values. | |
Bits 5-3 and 1-0 retain their original values, so S should be set before | |
booting. These bits are typically set to 0, although bit 5 is set for an RTE | |
reconfiguration boot, and bits 1-0 may be set if booting from a head other | |
than 0 is desired. | |
Implementation notes: | |
1. The Loader ROMs manual indicates that bits 2-0 select the head to use, | |
implying that heads 0-7 are valid. However, Table 5 has entries only for | |
heads 0-3, and the boot loader code will malfunction if heads 4-7 are | |
specified. The code masks the head number to three bits but forms the | |
Cold Load Read command by shifting the head number six bits to the left. | |
As the head field in the command is only two bits wide, specifying heads | |
4-7 will result in bit 2 being shifted into the opcode field, resulting | |
in a Recalibrate command. | |
*/ | |
const BOOT_ROM ds_rom = { | |
0017727, /* START JSB STAT GET STATUS */ | |
0002021, /* SSA,RSS IS DRIVE READY ? */ | |
0027742, /* JMP DMA YES, SET UP DMA */ | |
0013714, /* AND B20 NO, CHECK STATUS BITS */ | |
0002002, /* SZA IS DRIVE FAULTY OR HARD DOWN ? */ | |
0102030, /* HLT 30B YES, HALT 30B, "RUN" TO TRY AGAIN */ | |
0027700, /* JMP START NO, TRY AGAIN FOR DISC READY */ | |
0102011, /* ADDR1 OCT 102011 */ | |
0102055, /* ADDR2 OCT 102055 */ | |
0164000, /* CNT DEC -6144 */ | |
0000007, /* D7 OCT 7 */ | |
0001400, /* STCMD OCT 1400 */ | |
0000020, /* B20 OCT 20 */ | |
0017400, /* STMSK OCT 17400 */ | |
0000000, /* NOP */ | |
0000000, /* NOP */ | |
0000000, /* NOP */ | |
0000000, /* NOP */ | |
0000000, /* NOP */ | |
0000000, /* NOP */ | |
0000000, /* NOP */ | |
0000000, /* NOP */ | |
0000000, /* NOP */ | |
0000000, /* STAT NOP STATUS CHECK SUBROUTINE */ | |
0107710, /* CLC DC,C SET STATUS COMMAND MODE */ | |
0063713, /* LDA STCMD GET STATUS COMMAND */ | |
0102610, /* OTA DC OUTPUT STATUS COMMAND */ | |
0102310, /* SFS DC WAIT FOR STATUS#1 WORD */ | |
0027733, /* JMP *-1 */ | |
0107510, /* LIB DC,C B-REG = STATUS#1 WORD */ | |
0102310, /* SFS DC WAIT FOR STATUS#2 WORD */ | |
0027736, /* JMP *-1 */ | |
0103510, /* LIA DC,C A-REG = STATUS#2 WORD */ | |
0127727, /* JMP STAT,I RETURN */ | |
0067776, /* DMA LDB DMACW GET DMA CONTROL WORD */ | |
0106606, /* OTB 6 OUTPUT DMA CONTROL WORD */ | |
0067707, /* LDB ADDR1 GET MEMORY ADDRESS */ | |
0106702, /* CLC 2 SET MEMORY ADDRESS INPUT MODE */ | |
0106602, /* OTB 2 OUTPUT MEMORY ADDRESS TO DMA */ | |
0102702, /* STC 2 SET WORD COUNT INPUT MODE */ | |
0067711, /* LDB CNT GET WORD COUNT */ | |
0106602, /* OTB 2 OUTPUT WORD COUNT TO DMA */ | |
0106710, /* CLDLD CLC DC SET COMMAND INPUT MODE */ | |
0102501, /* LIA 1 LOAD SWITCH */ | |
0106501, /* LIB 1 REGISTER SETTINGS */ | |
0013712, /* AND D7 ISOLATE HEAD NUMBER */ | |
0005750, /* BLF,CLE,SLB BIT 12=0? */ | |
0027762, /* JMP *+3 NO,MANUAL BOOT */ | |
0002002, /* SZA YES,RPL BOOT. HEAD#=0? */ | |
0001000, /* ALS NO,HEAD#1, MAKE HEAD#=2 */ | |
0001720, /* ALF,ALS FORM COLD LOAD */ | |
0001000, /* ALS COMMAND WORD */ | |
0103706, /* STC 6,C ACTIVATE DMA */ | |
0103610, /* OTA DC,C OUTPUT COLD LOAD COMMAND */ | |
0102310, /* SFS DC IS COLD LOAD COMPLETED ? */ | |
0027766, /* JMP *-1 NO, WAIT */ | |
0017727, /* JSB STAT YES, GET STATUS */ | |
0060001, /* LDA 1 */ | |
0013715, /* AND STMSK A-REG = STATUS BITS OF STATUS#1 WD */ | |
0002002, /* SZA IS TRANSFER OK ? */ | |
0027700, /* JMP START NO,TRY AGAIN */ | |
0117710, /* EXIT JSB ADDR2,I YES, EXEC LOADED PROGRAM _@ 2055B */ | |
0000010, /* DMACW ABS DC */ | |
0170100, /* ABS -START */ | |
}; | |
t_stat ds_boot (int32 unitno, DEVICE *dptr) | |
{ | |
if (unitno != 0) /* boot supported on drive unit 0 only */ | |
return SCPE_NOFNC; /* report "Command not allowed" if attempted */ | |
cpu_ibl (ds_rom, ds_dib.select_code, /* copy the boot ROM to memory and configure */ | |
IBL_OPT | IBL_DS_HEAD, /* the S register accordingly */ | |
IBL_DS | IBL_MAN | IBL_SET_SC (ds_dib.select_code)); | |
return SCPE_OK; | |
} | |
/* MAC disc global SCP routines */ | |
/* Load or unload the drive heads. | |
The SCP command SET DSn UNLOADED simulates setting the hardware RUN/STOP | |
switch to STOP. The heads are unloaded, and the drive is spun down. | |
The SET DSn LOADED command simulates setting the switch to RUN. The drive is | |
spun up, and the heads are loaded. | |
The library handles command validation and setting the appropriate drive unit | |
status. | |
*/ | |
t_stat ds_load_unload (UNIT *uptr, int32 value, CONST char *cptr, void *desc) | |
{ | |
const t_bool load = (value != UNIT_UNLOAD); /* true if the heads are loading */ | |
return dl_load_unload (&mac_cntlr, uptr, load); /* load or unload the heads */ | |
} | |
/* MAC disc local utility routines */ | |
/* Start a command. | |
The previously prepared command is executed by calling the corresponding | |
library routine. On entry, the controller's opcode field contains the | |
command to start, and the buffer contains the command word in element 0 and | |
the parameters required by the command, if any, beginning in element 1. | |
If the command started, the returned pointer will point at the unit to | |
activate (if that unit's "wait" field is non-zero). If the returned pointer | |
is NULL, the command failed to start, and the controller status has been set | |
to indicate the reason. The interface flag is set to notify the CPU of the | |
failure. | |
Implementation notes: | |
1. If a command that accesses the drive is attempted on a drive currently | |
seeking, the returned pointer will be valid, but the unit's "wait" time | |
will be zero. The unit must not be activated (as it already is active). | |
When the seek completes, the command will be executed automatically. | |
If a Seek or Cold Load Read command is attempted on a drive currently | |
seeking, seek completion will occur normally, but Seek Check status will | |
be set. | |
2. For debug printouts, we want to print the name of the command (Seek or | |
Recalibrate) in progress when a new command is started. However, when | |
the library routine returns, the unit operation and controller opcode | |
have been changed to reflect the new command. Therefore, we must record | |
the operation in progress before calling the library. | |
The problem is in determining which unit's operation code to record. We | |
cannot blindly use the unit field from the new command, as recorded in | |
the controller, as preparation has ensured only that the target unit | |
number is legal but not necessarily valid. Therefore, we must validate | |
the unit number before accessing the unit's operation code. | |
If the unit number is invalid, the command will not start, but the | |
compiler does not know this. Therefore, we must ensure that the saved | |
operation code is initialized, or a "variable used uninitialized" warning | |
will occur. | |
*/ | |
static void start_command (void) | |
{ | |
int32 unit, time; | |
UNIT *uptr; | |
CNTLR_OPCODE drive_command; | |
unit = GET_S1UNIT (mac_cntlr.spd_unit); /* get the (prepared) unit from the command */ | |
if (unit <= DL_MAXDRIVE) /* is the unit number valid? */ | |
drive_command = (CNTLR_OPCODE) ds_unit [unit].OP; /* get the opcode from the unit that will be used */ | |
else /* the unit is invalid, so the command will not start */ | |
drive_command = End; /* but the compiler doesn't know this! */ | |
uptr = dl_start_command (&mac_cntlr, ds_unit, DL_MAXDRIVE); /* ask the controller to start the command */ | |
if (uptr) { /* did the command start? */ | |
time = uptr->wait; /* save the activation time */ | |
if (time) /* was the unit scheduled? */ | |
activate_unit (uptr); /* activate it (and clear the "wait" field) */ | |
if (time == 0) /* was the unit busy? */ | |
tprintf (ds_dev, DEB_RWSC, "Unit %d %s in progress\n", | |
uptr - ds_unit, dl_opcode_name (MAC, drive_command)); | |
if (uptr - ds_unit > DL_MAXDRIVE) | |
tprintf (ds_dev, DEB_RWSC, "Controller %s command initiated\n", | |
dl_opcode_name (MAC, mac_cntlr.opcode)); | |
else | |
tprintf (ds_dev, DEB_RWSC, "Unit %d position %" T_ADDR_FMT "d %s command initiated\n", | |
uptr - ds_unit, uptr->pos, dl_opcode_name (MAC, mac_cntlr.opcode)); | |
} | |
else /* the command failed to start */ | |
ds_io (&ds_dib, ioENF, 0); /* so set the flag to notify the CPU */ | |
return; | |
} | |
/* Poll the interface for a new command. | |
If a new command is available, and the controller is not busy, prepare the | |
command for execution. If preparation succeeded, and the command needs | |
parameters before executing, set the flag to request the first one from the | |
CPU. If no parameters are needed, the command is ready to execute. | |
If preparation failed, set the flag to notify the CPU. The controller | |
status contains the reason for the failure. | |
*/ | |
static void poll_interface (void) | |
{ | |
if (ds.cmrdy == SET && mac_cntlr.state != cntlr_busy) { /* are the interface and controller ready? */ | |
buffer [0] = fifo_unload (); /* unload the command into the buffer */ | |
if (dl_prepare_command (&mac_cntlr, ds_unit, DL_MAXDRIVE)) { /* prepare the command; did it succeed? */ | |
if (mac_cntlr.length) /* does the command require parameters? */ | |
ds_io (&ds_dib, ioENF, 0); /* set the flag to request the first one */ | |
else /* if not waiting for parameters */ | |
start_command (); /* start the command */ | |
} | |
else /* preparation failed */ | |
ds_io (&ds_dib, ioENF, 0); /* so set the flag to notify the CPU */ | |
ds.cmrdy = CLEAR; /* flush the command from the interface */ | |
} | |
return; | |
} | |
/* Poll the drives for attention requests. | |
If the controller is idle and interrupts are allowed, the drives are polled | |
to see if any drive is requesting attention. If one is found, the controller | |
resets that drive's Attention status, saves the drive's unit number, sets | |
Drive Attention status, and waits for a command from the CPU. The interface | |
sets the flag to notify the CPU. | |
*/ | |
static void poll_drives (void) | |
{ | |
if (mac_cntlr.state == cntlr_idle && ds.control == SET) /* is the controller idle and interrupts are allowed? */ | |
if (dl_poll_drives (&mac_cntlr, ds_unit, DL_MAXDRIVE)) /* poll the drives; was Attention seen? */ | |
ds_io (&ds_dib, ioENF, 0); /* request an interrupt */ | |
return; | |
} | |
/* Load a word into the FIFO. | |
A word is loaded into the next available location in the FIFO, and the FIFO | |
occupancy count is incremented. If the FIFO is full on entry, the load is | |
ignored. | |
Implementation notes: | |
1. The FIFO is implemented as circular queue to take advantage of REG_CIRC | |
EXAMINE semantics. REG->qptr is the index of the first word currently in | |
the FIFO. By specifying REG_CIRC, examining FIFO[0-n] will always | |
display the words in load order, regardless of the actual array index of | |
the start of the list. The number of words currently present in the FIFO | |
is kept in fifo_count (0 = empty, 1-16 = number of words available). | |
If fifo_count < FIFO_SIZE, (REG->qptr + fifo_count) mod FIFO_SIZE is the | |
index of the new word location. Loading stores the word there and then | |
increments fifo_count. | |
2. Because the load and unload routines need access to qptr in the REG | |
structure for the FIFO array, a pointer to the REG is stored in the | |
fifo_reg variable during device reset. | |
*/ | |
static void fifo_load (uint16 data) | |
{ | |
uint32 index; | |
if (FIFO_FULL) { /* is the FIFO already full? */ | |
tprintf (ds_dev, DEB_BUF, "Attempted load to full FIFO, data %06o\n", data); | |
return; /* return with the load ignored */ | |
} | |
index = (ds.fifo_reg->qptr + ds.fifo_count) % FIFO_SIZE; /* calculate the index of the next available location */ | |
ds.fifo [index] = data; /* store the word in the FIFO */ | |
ds.fifo_count = ds.fifo_count + 1; /* increment the count of words stored */ | |
tprintf (ds_dev, DEB_BUF, "Data %06o loaded into FIFO (%d)\n", | |
data, ds.fifo_count); | |
return; | |
} | |
/* Unload a word from the FIFO. | |
A word is unloaded from the first location in the FIFO, and the FIFO | |
occupancy count is decremented. If the FIFO is empty on entry, the unload | |
returns dummy data. | |
Implementation notes: | |
1. If fifo_count > 0, REG->qptr is the index of the word to remove. Removal | |
gets the word and then increments qptr (mod FIFO_SIZE) and decrements | |
fifo_count. | |
*/ | |
static uint16 fifo_unload (void) | |
{ | |
uint16 data; | |
if (FIFO_EMPTY) { /* is the FIFO already empty? */ | |
tprintf (ds_dev, DEB_BUF, "Attempted unload from empty FIFO\n"); | |
return 0; /* return with no data */ | |
} | |
data = ds.fifo [ds.fifo_reg->qptr]; /* get the word from the FIFO */ | |
ds.fifo_reg->qptr = (ds.fifo_reg->qptr + 1) % FIFO_SIZE; /* update the FIFO queue pointer */ | |
ds.fifo_count = ds.fifo_count - 1; /* decrement the count of words stored */ | |
tprintf (ds_dev, DEB_BUF, "Data %06o unloaded from FIFO (%d)\n", | |
data, ds.fifo_count); | |
return data; | |
} | |
/* Clear the FIFO. | |
The FIFO is cleared by setting the occupancy counter to zero. | |
*/ | |
static void fifo_clear (void) | |
{ | |
ds.fifo_count = 0; /* clear the FIFO */ | |
tprintf (ds_dev, DEB_BUF, "FIFO cleared\n"); | |
return; | |
} | |
/* Activate the unit. | |
The specified unit is activated using the unit's "wait" time. If debugging | |
is enabled, the activation is logged to the debug file. | |
*/ | |
static t_stat activate_unit (UNIT *uptr) | |
{ | |
t_stat result; | |
if (uptr == &ds_cntlr) | |
tprintf (ds_dev, DEB_SERV, "Controller delay %d service scheduled\n", | |
uptr->wait); | |
else | |
tprintf (ds_dev, DEB_SERV, "Unit %d delay %d service scheduled\n", | |
uptr - ds_unit, uptr->wait); | |
result = sim_activate (uptr, uptr->wait); /* activate the unit */ | |
uptr->wait = 0; /* reset the activation time */ | |
return result; /* return the activation status */ | |
} |