blob: 1da781bcd25ee0fe7eee72ab8979b608cdf461f3 [file] [log] [blame] [raw]
/* hp3000_ds.c: HP 3000 30229B Cartridge Disc Interface simulator
Copyright (c) 2016, 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
AUTHOR 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 the author 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 author.
DS HP 30229B Cartridge Disc Interface
21-Jul-15 JDB First release version
15-Jun-15 JDB Passes the cartridge disc diagnostic (D419A)
15-Feb-15 JDB Created
References:
- Model 7905A Cartridge Disc Subsystem Installation and Service Manual
(30129-90003, May 1976)
- Stand-Alone HP 30129A (7905A) Disc Cartridge Diagnostic
(30129-90007, February 1976)
- HP 3000 Series III Engineering Diagrams Set
(30000-90141, April 1980)
- 13037 Disc Controller Technical Information Package
(13037-90902, August 1980)
- 7925D Disc Drive Service Manual
(07925-90913, April 1984)
The HP 30129A Cartridge Disc Subsystem connects the 7905A, 7906A, 7920A, and
7925A disc drives to the HP 3000. The subsystem consists of a 30229B
Cartridge Disc Interface, a 13037D Multiple-Access Disc Controller ("MAC"),
and from one to eight MAC drives. The subsystem uses the Selector Channel to
achieve a 937.5 KB/second transfer rate to the CPU.
The disc controller connects from one to eight HP 7905 (15 MB), 7906 (20 MB),
7920 (50 MB), or 7925 (120 MB) disc drives to interfaces installed in from
one to eight CPUs. The drives use a common command set and present data to
the controller synchronously at a 468.75 kiloword per second (2.133
microseconds per word) data rate.
The disc interface is used to connect the HP 3000 CPU to the 13037's device
controller. While the controller supports multiple-CPU systems, the HP 3000
does not use this capability.
This module simulates a 30229B interface connected to a 13037D controller;
the controller simulation is provided by the hp_disclib module. 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. An extra unit for the use of the disc controller library is
also allocated.
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.
While the controller is in the busy state, command execution is broken up
into a series of phases. Phase transitions are scheduled on the drive units
for commands that access the drives and on the controller unit otherwise.
The interface unit service routine must call the disc controller to inform it
of these events.
The disc interface responds to direct and programmed I/O instructions, as
follows:
Control Word Format (CIO):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| M | R | T | - - - - - - - - - - - - - |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
M = master reset
R = reset interrupts
T = test mode
Test mode inhibits the flag bus signals. This allows the diagnostic to
exercise the interface without causing the disc controller to react.
Control Word Format (SIO Control):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| - - - - - - - - - - - - - - - | W | word 1
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| disc controller command word | word 2
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
W = set the Wait flip-flop
The command opcode is the disc controller command to execute. If the command
takes or returns parameters, an SIO Write or Read must follow the Control
order to supply or receive them.
Status Word Format (TIO and SIO Status):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| S | T | I |termination status | - - - - | unit number |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
S = SIO OK
T = test mode is enabled (also DIO OK)
I = interrupt request
The termination status and unit number report the success or failure of the
last disc controller command. Also, note that the test mode flip-flop output
is reported as DIO OK. This means that RIO and WIO are inhibited unless test
mode is set.
Output Data Word Format (WIO and SIO Write):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| data buffer register value |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Input Data Word Format (RIO and SIO Read):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| data buffer register value |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Disc read or write commands may transfer up to 4K words with a single SIO
Read or Write order. Chained orders are necessary if longer transfers are
required.
The interface allows the channel to retry a failed transfer without CPU
intervention. The controller asserts the DVEND signal for transfer errors
that it considers retryable (e.g., a disc read error). A channel program can
detect this condition via a Conditional Jump order, which will succeed for
each retryable failure until the retry count expires.
Unusually among HP 3000 interfaces, this device reacts to the PFWARN signal.
A pending power failure will abort the current disc transfer and channel
program, so that the operating system will know to retry the transfer once
power has been restored.
Implementation notes:
1. As only a single interface connected to the disc controller is supported,
the interface select address jumpers are not simulated. Instead, the
interface behaves as though it is always selected and does not process
the SELIF and DSCIF functions from the controller.
2. In hardware, jumper W1 selects whether the interface should assert the
CLEAR signal to the disc controller when the interface is preset. This
jumper is needed in a multiple-interface system so that only one
interface clears the controller. The simulation does check the state of
jumper W1, but as only a single interface is supported, the jumper
position is hard-coded as ENABLED rather than being configurable via the
user interface.
3. Several of the hardware flip-flops that directly drive flag signals to
the controller are modeled in simulation by setting and clearing the
corresponding bits in the flags word itself.
4. The simulation provides REALTIME and FASTTIME options. FASTTIME settings
may be altered via the register interface. Performing a power-on reset
(RESET -P) will restore the original FASTTIME values.
5. This simulation provides diagnostic override settings to allow complete
testing coverage via the offline disc diagnostic. See the comments in
the disc controller library for details of this capability.
*/
#include "hp3000_defs.h"
#include "hp3000_io.h"
#include "hp_disclib.h"
/* Program constants */
#define DRIVE_COUNT (DL_MAXDRIVE + 1) /* number of disc drive units */
#define UNIT_COUNT (DRIVE_COUNT + DL_AUXUNITS) /* total number of units */
#define ds_cntlr ds_unit [DL_MAXDRIVE + 1] /* controller unit alias */
#define OVERRIDE_COUNT 50 /* count of diagnostic override entries */
#define PRESET_ENABLE TRUE /* Preset Jumper (W1) is enabled */
#define UNUSED_COMMANDS (BUSY | DSCIF | SELIF | IFPRF | STDFL | FREE) /* unused disc interface commands */
/* Debug flags (interface-specific) */
#define DEB_IOB DL_DEB_IOB /* trace I/O bus signals and data words */
#define DEB_CSRW (1 << DL_DEB_V_UF + 0) /* trace control, status, read, and write commands */
/* Control word.
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| M | R | T | - - - - - - - - - - - - - | DIO
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| - - - - - - - - - - - - - - - | W | PIO word 1
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| disc controller command word | PIO word 2
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
#define CN_MR 0100000 /* (M) master reset */
#define CN_RIN 0040000 /* (R) reset interrupt */
#define CN_TEST 0020000 /* (T) test mode */
#define CN_WAIT 0000001 /* (W) wait for data */
#define CN_OPCODE_MASK 0017400 /* command word opcode mask */
#define CN_OPCODE_SHIFT 8 /* controller opcode alignment shift */
#define CN_OPCODE(c) ((CNTLR_OPCODE) (((c) & CN_OPCODE_MASK) >> CN_OPCODE_SHIFT))
static const BITSET_NAME control_names [] = { /* Control word names */
"master reset", /* bit 0 */
"reset interrupt", /* bit 1 */
"test mode" /* bit 2 */
};
static const BITSET_FORMAT control_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (control_names, 13, msb_first, no_alt, no_bar) };
/* Status word.
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| S | T | I |termination status | - - - - | unit number |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
#define ST_SIO_OK 0100000 /* (S) SIO OK to use */
#define ST_TEST 0040000 /* (T) test mode enabled */
#define ST_INTREQ 0020000 /* (I) interrupt requested */
#define ST_STATUS_MASK 0017400 /* encoded termination status mask */
#define ST_UNIT_MASK 0000017 /* unit number mask */
#define ST_MASK ~(ST_SIO_OK | ST_TEST | ST_INTREQ)
#define ST_STATUS_SHIFT 8 /* termination status alignment shift */
#define ST_UNIT_SHIFT 0 /* unit number alignment shift */
#define ST_STATUS(n) ((n) << ST_STATUS_SHIFT & ST_STATUS_MASK)
#define ST_TO_UNIT(s) (((s) & ST_UNIT_MASK) >> ST_UNIT_SHIFT)
#define ST_TO_STATUS(s) (((s) & ST_STATUS_MASK) >> ST_STATUS_SHIFT)
static const BITSET_NAME status_names [] = { /* Status word names */
"SIO OK", /* bit 0 */
"test mode", /* bit 1 */
"interrupt" /* bit 2 */
};
static const BITSET_FORMAT status_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (status_names, 13, msb_first, no_alt, append_bar) };
/* Disc controller library data structures */
#define DS_SEEK_ONE uS (25) /* track-to-track seek time */
#define DS_SEEK_FULL uS (250) /* full-stroke seek time */
#define DS_SECTOR_FULL uS (50) /* full sector rotation time */
#define DS_DATA_XFER uS (1) /* data transfer response time */
#define DS_ISG uS (25) /* intersector gap rotation time */
#define DS_OVERHEAD uS (25) /* controller execution overhead */
static DELAY_PROPS fast_times = /* FASTTIME delays */
{ DELAY_INIT (DS_SEEK_ONE, DS_SEEK_FULL,
DS_SECTOR_FULL, DS_DATA_XFER,
DS_ISG, DS_OVERHEAD) } ;
static DIAG_ENTRY overrides [OVERRIDE_COUNT] = { /* diagnostic overrides array */
{ DL_OVEND }
};
/* Interface state */
static FLIP_FLOP sio_busy = CLEAR; /* SIO busy flip-flop */
static FLIP_FLOP device_sr = CLEAR; /* device service request flip-flop */
static FLIP_FLOP input_xfer = CLEAR; /* input transfer flip-flop */
static FLIP_FLOP output_xfer = CLEAR; /* output transfer flip-flop */
static FLIP_FLOP interrupt_mask = CLEAR; /* interrupt mask flip-flop */
static FLIP_FLOP jump_met = CLEAR; /* jump met flip-flop */
static FLIP_FLOP device_end = CLEAR; /* device end flip-flop */
static FLIP_FLOP data_overrun = CLEAR; /* data overrun flip-flop */
static FLIP_FLOP end_of_data = CLEAR; /* end of data flip-flop */
static FLIP_FLOP test_mode = CLEAR; /* test mode flip-flop */
static FLIP_FLOP data_wait = CLEAR; /* wait flip-flop */
static uint16 status_word = 0; /* status register */
static uint16 buffer_word = 0; /* data buffer register */
static uint16 retry_counter = 0; /* retry counter */
static CNTLR_FLAG_SET flags = 0; /* disc controller interface flag set */
static uint16 buffer [DL_BUFSIZE]; /* command/status/sector buffer */
DEVICE ds_dev; /* incomplete device structure */
static CNTLR_VARS mac_cntlr = /* MAC controller */
{ CNTLR_INIT (MAC, ds_dev, buffer, overrides, fast_times) };
/* Interface local SCP support routines */
static CNTLR_INTRF ds_interface;
static t_stat ds_service (UNIT *uptr);
static t_stat ds_reset (DEVICE *dptr);
static t_stat ds_boot (int32 unit_number, DEVICE *dptr);
static t_stat ds_attach (UNIT *uptr, char *cptr);
static t_stat ds_detach (UNIT *uptr);
static t_stat ds_load_unload (UNIT *uptr, int32 value, char *cptr, void *desc);
/* Interface local utility routines */
static void master_reset (void);
static void deny_sio_busy (void);
static void clear_interface_logic (DIB *dibptr);
static void call_controller (UNIT *uptr);
/* Interface SCP data structures */
/* Device information block */
static DIB ds_dib = {
&ds_interface, /* device interface */
4, /* device number */
SRNO_UNUSED, /* service request number */
4, /* interrupt priority */
INTMASK_E /* interrupt mask */
};
/* Unit list */
#define UNIT_FLAGS (UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | UNIT_DISABLE | UNIT_UNLOAD)
static UNIT ds_unit [UNIT_COUNT] = {
{ UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) }, /* drive unit 0 */
{ UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) }, /* drive unit 1 */
{ UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) }, /* drive unit 2 */
{ UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) }, /* drive unit 3 */
{ UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) }, /* drive unit 4 */
{ UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) }, /* drive unit 5 */
{ UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) }, /* drive unit 6 */
{ UDATA (&ds_service, UNIT_FLAGS | UNIT_7905, WORDS_7905) }, /* drive unit 7 */
{ UDATA (&ds_service, UNIT_DIS, 0) } /* controller unit */
};
/* Register list */
static REG ds_reg [] = {
/* Macro Name Location Width Offset Flags */
/* ------ ------ --------------- ----- ------ ------------------------- */
{ FLDATA (SIOBSY, sio_busy, 0) },
{ FLDATA (DEVSR, device_sr, 0) },
{ FLDATA (INXFR, input_xfer, 0) },
{ FLDATA (OUTXFR, output_xfer, 0) },
{ FLDATA (INTMSK, interrupt_mask, 0) },
{ FLDATA (JMPMET, jump_met, 0) },
{ FLDATA (DEVEND, device_end, 0) },
{ FLDATA (DATOVR, data_overrun, 0) },
{ FLDATA (ENDDAT, end_of_data, 0) },
{ FLDATA (TEST, test_mode, 0) },
{ FLDATA (WAIT, data_wait, 0) },
{ FLDATA (CLEAR, flags, 0) },
{ FLDATA (CMRDY, flags, 1) },
{ FLDATA (DTRDY, flags, 2) },
{ FLDATA (EOD, flags, 3) },
{ FLDATA (INTOK, flags, 4) },
{ FLDATA (OVRUN, flags, 5) },
{ FLDATA (XFRNG, flags, 6) },
{ ORDATA (BUFFER, buffer_word, 16), REG_A | REG_FIT | PV_RZRO },
{ ORDATA (STATUS, status_word, 16), REG_FIT | PV_RZRO },
{ DRDATA (RETRY, retry_counter, 4), REG_FIT | PV_LEFT },
{ SRDATA (DIAG, overrides), REG_HRO },
{ SRDATA (DIB, ds_dib), REG_HRO },
DL_REGS (mac_cntlr, ds_unit, UNIT_COUNT, buffer, fast_times),
{ NULL }
};
/* Modifier list */
static MTAB ds_mod [] = {
DL_MODS (mac_cntlr, ds_load_unload, OVERRIDE_COUNT),
/* Entry Flags Value Print String Match String Validation Display Descriptor */
/* ----------- ----------- ------------ ------------ ------------ ------------- ---------------- */
{ MTAB_XDV, VAL_DEVNO, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &ds_dib },
{ MTAB_XDV, VAL_INTMASK, "INTMASK", "INTMASK", &hp_set_dib, &hp_show_dib, (void *) &ds_dib },
{ MTAB_XDV, VAL_INTPRI, "INTPRI", "INTPRI", &hp_set_dib, &hp_show_dib, (void *) &ds_dib },
{ 0 }
};
/* Debugging trace list */
static DEBTAB ds_deb [] = {
{ "CMD", DL_DEB_CMD }, /* controller commands */
{ "INCO", DL_DEB_INCO }, /* controller command initiations and completions */
{ "CSRW", DEB_CSRW }, /* interface control, status, read, and write actions */
{ "STATE", DL_DEB_STATE }, /* controller execution state changes */
{ "SERV", DL_DEB_SERV }, /* controller unit service scheduling calls */
{ "XFER", DL_DEB_XFER }, /* controller data reads and writes */
{ "IOBUS", DEB_IOB }, /* interface and controller I/O bus signals and data words */
{ NULL, 0 }
};
/* Device descriptor */
DEVICE ds_dev = {
"DS", /* device name */
ds_unit, /* unit array */
ds_reg, /* register array */
ds_mod, /* modifier array */
UNIT_COUNT, /* 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 */
};
/* Interface local SCP support routines */
/* Disc controller interface.
The interface is installed on the IOP and Selector Channel buses and receives
direct and programmed I/O commands from the IOP and Selector Channel,
respectively. In simulation, the asserted signals on the buses are
represented as bits in the inbound_signals set. Each signal is processed
sequentially in numerical order, and a set of similar outbound_signals is
assembled and returned to the caller, simulating assertion of the
corresponding backplane signals.
MAC disc controller commands take from 0 to 2 parameters and return from 0 to
7 status words. All communication with the disc controller is via programmed
I/O. Direct I/O is used only to communicate with the interface.
Commands consist of a Control I/O order optionally followed by a one- or
two-word Write order to supply the parameters. Commands that return status
consist of a Control order followed by a Read order to send the status.
Controller command opcodes are carried in the IOAW of a programmed I/O
Control order. The IOCW is not used, except for bit 15, which is clocked
into the WAIT flip-flop. This bit must be set for commands that return
parameters (Request Status, Request Sector Address, Request Syndrome, and
Request Disc Address) to hold off the controller until the channel has
executed a Read I/O order. Setting WAIT asserts DTRDY unconditionally; the
controller will not send a word to the CPU until DTRDY denies, indicating
that the interface data buffer is empty and ready to receive the word.
Implementation notes:
1. In hardware, the disc controller executes a status command, such as
Request Status, by first asserting IFGTC to clear the command from the
interface and then asserting IFIN to tell the interface that the (first)
status word is ready for pickup. Both IFGTC and IFIN assert CHANSR to
the channel; the first completes the Control I/O order, and the second
completes the TOGGLEINXFER phase of the Read I/O order. Simulating this
sequential assertion requires two calls to the controller. The second
call is placed the TOGGLEINXFER handler, although in hardware this signal
has no effect on the controller state.
2. In hardware, the PREADSTB and PWRITESTB signals each toggle the Data
Ready flip-flop, rather than explicitly clearing and setting it,
respectively. The simulation maintains this action.
3. In hardware, three serially connected End of Data flip-flops are
employed. The first presets on EOT, the second clocks on the leading
edge of TOGGLEXFER, and the third clocks when the Data Ready flip-flop
clears. The output of the third drives the EOD line to the disc
controller. For a read, DTRDY denies when the trailing edge of PREADSTB
clocks the third flip-flop. For this to work, the implied relationship
is EOT asserts before the leading edge of TOGGLEINXFER, which asserts
before the trailing edge of PREADSTB.
In simulation, PREADSTB is processed before EOT, which is processed
before TOGGLEINXFER, which is the order of the leading edges of the
hardware signals. To simulate clocking the Data Ready flip-flop on the
trailing edge, the action is performed in the TOGGLEINXFER handler
instead of the PREADSTB handler.
4. In hardware, the Device SR 1 flip-flop is cleared by assertion of the
PCONTSTB or PWRITESTB signals, and the the Device SR 2 flip-flop is
cleared by assertion of CHANSO without DEVEND or by the clear output of
the SIO busy flip-flop. Also, DEVEND forces CHANSR assertion. In
simulation, a unified device_sr flip-flop is employed that is cleared if
CHANSO is asserted or SIO Busy is clear.
5. When TOGGLESIOOK clears the sio_busy flip-flop, the controller must be
called to poll the drives for attention. Consider an SIO program that
does a Seek on drive 0, followed by a Read on drive 1, followed by an
End. If the seek completes during the read, the Drive Attention
interrupt won't occur after the End unless the drive is polled from the
TOGGLESIOOK handler, as INTOK isn't asserted until the channel program
ends.
6. Receipt of a DRESETINT signal clears the interrupt request and active
flip-flops but does not cancel a request pending but not yet serviced by
the IOP. However, when the IOP does service the request by asserting
INTPOLLIN, the interface routine returns INTPOLLOUT, which will cancel
the request.
*/
static SIGNALS_DATA ds_interface (DIB *dibptr, INBOUND_SET inbound_signals, uint16 inbound_value)
{
INBOUND_SIGNAL signal;
INBOUND_SET working_set = inbound_signals;
uint16 outbound_value = 0;
OUTBOUND_SET outbound_signals = NO_SIGNALS;
dprintf (ds_dev, DEB_IOB, "Received data %06o with signals %s\n",
inbound_value, fmt_bitset (inbound_signals, inbound_format));
if (inbound_signals & CHANSO || sio_busy == CLEAR) /* if a PIO signal is asserted or SIO is inactive */
device_sr = CLEAR; /* then clear the device SR flip-flop */
while (working_set) {
signal = IONEXTSIG (working_set); /* isolate the next signal */
switch (signal) { /* dispatch an I/O signal */
case SETINT:
case DSETINT:
dibptr->interrupt_request = SET; /* request an interrupt */
if (interrupt_mask) /* if the interrupt mask is satisfied */
outbound_signals |= INTREQ; /* then assert the INTREQ signal */
break;
case DRESETINT:
dibptr->interrupt_active = CLEAR; /* reset the interrupt active flip-flop */
break;
case DSETMASK:
interrupt_mask = (dibptr->interrupt_mask /* set the mask flip-flop */
& inbound_value) != 0; /* from the mask bit and the mask value */
if (interrupt_mask && dibptr->interrupt_request) /* if the mask is enabled and a request is pending */
outbound_signals |= INTREQ; /* then assert the INTREQ signal */
break;
case DCONTSTB:
dprintf (ds_dev, DEB_CSRW, "Control is %s\n",
fmt_bitset (inbound_value, control_format));
if (inbound_value & CN_MR) /* if the master reset bit is set */
master_reset (); /* then reset the interface */
if (inbound_value & CN_RIN) /* if the reset interrupt bit is set */
dibptr->interrupt_request = CLEAR; /* then clear the interrupt request */
test_mode = (inbound_value & CN_TEST) != 0; /* set the test mode flip-flop from the test bit */
break;
case PSTATSTB:
case DSTATSTB:
outbound_value = status_word; /* get the controller status */
if (sio_busy == CLEAR && sel_is_idle) /* if the interface and channel are inactive */
outbound_value |= ST_SIO_OK; /* then add the SIO OK status bit */
if (test_mode == SET) /* if test mode is enabled */
outbound_value |= ST_TEST; /* then add the DIO OK status bit */
if (dibptr->interrupt_request == SET) /* if an interrupt request is pending */
outbound_value |= ST_INTREQ; /* then add the IRQ status bit */
dprintf (ds_dev, DEB_CSRW, "Status is %s%s | unit %d\n",
fmt_bitset (outbound_value, status_format),
dl_status_name (ST_TO_STATUS (outbound_value)),
ST_TO_UNIT (outbound_value));
break;
case DREADSTB:
outbound_value = buffer_word; /* return the data buffer register value */
dprintf (ds_dev, DEB_CSRW, "Buffer value %06o returned\n",
outbound_value);
break;
case DWRITESTB:
dprintf (ds_dev, DEB_CSRW, "Buffer value %06o set\n",
inbound_value);
buffer_word = inbound_value; /* set the data buffer register value */
break;
case DSTARTIO:
dprintf (ds_dev, DEB_CSRW, "Channel program started\n");
sio_busy = SET; /* set the SIO busy flip-flop */
flags &= ~INTOK; /* and clear the interrupt OK flag */
sel_assert_REQ (dibptr); /* request the channel */
break;
case TOGGLESIOOK:
TOGGLE (sio_busy); /* set or clear the SIO busy flip-flop */
if (sio_busy == CLEAR) { /* if the flip-flop was cleared */
deny_sio_busy (); /* then reset the associated devices */
dprintf (ds_dev, DEB_CSRW, "Channel program ended\n");
call_controller (NULL); /* check for drive attention held off by INTOK denied */
}
break;
case TOGGLEINXFER:
TOGGLE (input_xfer); /* set or clear the input transfer flip-flop */
if (input_xfer == SET) /* if the transfer is starting */
call_controller (NULL); /* then let the controller know to output the first word */
else if (end_of_data == SET) /* otherwise if EOT is asserted */
flags |= EOD; /* then PREADSTB has cleared DTRDY */
break;
case TOGGLEOUTXFER:
TOGGLE (output_xfer); /* set or clear the output transfer flip-flop */
if (output_xfer == SET) /* if the transfer is starting */
device_sr = SET; /* then request the first word from the channel */
break;
case PCMD1:
data_wait = inbound_value & CN_WAIT; /* set the wait flip-flop from the supplied value */
if (data_wait == SET) /* if the wait flip-flip is set */
flags |= DTRDY; /* then the data ready flag is forced true */
device_sr = SET; /* request the second control word */
dprintf (ds_dev, DEB_CSRW, "Control is %s wait\n",
(data_wait == SET ? "set" : "clear"));
break;
case PCONTSTB:
dprintf (ds_dev, DEB_CSRW, "Control is %06o (%s)\n",
inbound_value, dl_opcode_name (MAC, CN_OPCODE (inbound_value)));
buffer_word = inbound_value; /* store the command in the data buffer register */
flags |= CMRDY; /* and set the command ready flag */
call_controller (NULL); /* tell the controller to start the command */
break;
case PREADSTB:
outbound_value = buffer_word; /* return the data buffer register value */
flags ^= DTRDY; /* and toggle (clear) the data ready flag */
call_controller (NULL); /* tell the controller that the buffer is empty */
break;
case PWRITESTB:
buffer_word = inbound_value; /* save the word to write */
flags ^= DTRDY; /* and toggle (set) the data ready flag */
if (inbound_signals & TOGGLEOUTXFER) /* EOT asserted with TOGGLEOUTXFER */
end_of_data = SET; /* sets the End of Data flip-flop */
call_controller (NULL); /* tell the controller that the buffer is full */
break;
case EOT:
if (inbound_signals & TOGGLEINXFER) /* EOT asserted with TOGGLEINXFER */
end_of_data = SET; /* sets the End of Data flip-flop */
break;
case INTPOLLIN:
if (dibptr->interrupt_request) { /* if a request is pending */
dibptr->interrupt_request = CLEAR; /* then clear it */
dibptr->interrupt_active = SET; /* and mark it now active */
outbound_signals |= INTACK; /* acknowledge the interrupt */
outbound_value = dibptr->device_number; /* and return our device number */
}
else /* otherwise the request has been reset */
outbound_signals |= INTPOLLOUT; /* so let the IOP know to cancel it */
break;
case XFERERROR:
case PFWARN:
dprintf (ds_dev, DEB_CSRW, "Channel program aborted\n");
flags |= XFRNG; /* set the transfer error flag */
clear_interface_logic (dibptr); /* and clear the interface to abort the transfer */
break;
case SETJMP:
if (jump_met == SET) /* if the jump met flip-flop is set */
outbound_signals |= JMPMET; /* then assert the JMPMET signal */
jump_met = CLEAR; /* reset the flip-flop */
break;
case CHANSO:
if (device_end == SET) { /* if the device end flip-flop is set */
outbound_signals |= DEVEND | CHANSR; /* then assert DEVEND and CHANSR to the channel */
device_end = input_xfer | output_xfer; /* clear device end if the transfer has stopped */
}
else if (device_sr == SET || test_mode == SET) /* if the interface requests service */
outbound_signals |= CHANSR; /* then assert CHANSR to the channel */
outbound_signals |= CHANACK; /* assert CHANACK to acknowledge the signal */
break;
case READNEXTWD: /* not used by this interface */
case ACKSR: /* not used by this interface */
case DEVNODB: /* not used by this interface */
case TOGGLESR: /* not used by this interface */
break;
}
IOCLEARSIG (working_set, signal); /* remove the current signal from the set */
}
dprintf (ds_dev, DEB_IOB, "Returned data %06o with signals %s\n",
outbound_value, fmt_bitset (outbound_signals, outbound_format));
return IORETURN (outbound_signals, outbound_value); /* return the outbound signals and value */
}
/* Service a controller or drive unit.
The service routine is called to execute scheduled controller command phases
for the specified unit. The actions to be taken depend on the current state
of the controller and the drive unit.
This routine is entered for three general reasons:
1. A disc unit is ready to execute the next command phase.
2. The controller unit is ready to execute the next command phase.
3. The controller unit has timed out while waiting for a new command.
Generally, the controller library handles all of the disc operations. All
that is necessary is to notify the controller, which will process the next
phase of command execution. Because the controller can overlap operations,
in particular scheduling seeks on several drive units simultaneously, each
drive unit carries its own current operation code and execution phase. The
controller uses these to determine what to do next.
*/
static t_stat ds_service (UNIT *uptr)
{
dprintf (ds_dev, DL_DEB_SERV, (uptr == &ds_cntlr
? "Controller unit service entered\n"
: "Unit %d service entered\n"),
uptr - &ds_unit [0]);
call_controller (uptr); /* call the controller */
if (device_sr == SET) /* if the interface requests service */
sel_assert_CHANSR (&ds_dib); /* then assert CHANSR to the channel */
return SCPE_OK;
}
/* Device reset routine.
This routine is called for a RESET, RESET DS, or BOOT DS command. It is the
simulation equivalent of the IORESET signal, which is asserted by the front
panel LOAD and DUMP switches.
For this interface, IORESET is identical to the programmed master reset. In
addition, if a power-on reset (RESET -P) is done, the original FASTTIME
settings are restored.
*/
static t_stat ds_reset (DEVICE *dptr)
{
master_reset (); /* perform a master reset */
if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */
fast_times.seek_one = DS_SEEK_ONE; /* then reset the track-to-track seek time, */
fast_times.seek_full = DS_SEEK_FULL; /* the full-stroke seek time, */
fast_times.sector_full = DS_SECTOR_FULL; /* the full-sector rotation time, */
fast_times.data_xfer = DS_DATA_XFER; /* the per-word data transfer time, */
fast_times.intersector_gap = DS_ISG; /* the intersector gap time, */
fast_times.overhead = DS_OVERHEAD; /* and the controller execution overhead */
}
return SCPE_OK;
}
/* Device boot routine.
This routine is called for the BOOT DS command to initiate the system cold
load procedure for the disc. It is the simulation equivalent to presetting
the System Switch Register to the appropriate control and device number bytes
and then pressing the ENABLE+LOAD front panel switches.
For this interface, the switch register is set to %0000nn, where "nn"
is the current disc interface device number, which defaults to 4. The
control byte is 0 (Cold Load Read).
The cold load procedure always uses unit 0.
*/
static t_stat ds_boot (int32 unit_number, DEVICE *dptr)
{
if (unit_number != 0) /* if a unit other than 0 is specified */
return SCPE_ARG; /* then fail with an invalid argument error */
else { /* otherwise */
cpu_front_panel (TO_WORD (Cold_Load_Read, /* set up the cold load */
ds_dib.device_number), /* from disc unit 0 */
Cold_Load);
return SCPE_OK; /* return to run the bootstrap */
}
}
/* Attach a disc image file to a drive unit.
The specified file is attached to the indicated drive unit. This is the
simulation equivalent to inserting a disc pack into the drive and setting
the RUN/STOP switch to RUN, which will load the heads and set the First
Status and Attention bits in the drive status.
The controller library routine handles command validation and setting the
appropriate drive unit status. It will return an error code if the command
fails. Otherwise, it will return SCPE_INCOMP if the command must be
completed with a controller call or SCPE_OK if the command is complete. If
the controller is idle, a call will be needed to poll the drives for
attention; otherwise, the drives will be polled the next time the controller
becomes idle.
Implementation notes:
1. If we are called during a RESTORE command to reattach a file previously
attached when the simulation was SAVEd, the unit status will not be
changed by the controller, so the unit will not request attention.
*/
static t_stat ds_attach (UNIT *uptr, char *cptr)
{
t_stat result;
result = dl_attach (&mac_cntlr, uptr, cptr); /* attach the drive */
if (result == SCPE_INCOMP) { /* if the controller must be called before returning */
call_controller (NULL); /* then let it know to poll the drives */
return SCPE_OK; /* before returning with success */
}
else /* otherwise */
return result; /* return the status of the attach */
}
/* Detach a disc image file from a drive unit.
The specified file is detached from the indicated drive unit. This is the
simulation equivalent to setting the RUN/STOP switch to STOP and removing the
disc pack from the drive. Stopping the drive will unload the heads and set
the Attention bit in the drive status.
The controller library routine handles command validation and setting the
appropriate drive unit status. It will return an error code if the command
fails. Otherwise, it will return SCPE_INCOMP if the command must be
completed with a controller call or SCPE_OK if the command is complete. If
the controller is idle, a call will be needed to poll the drives for
attention; otherwise, the drives will be polled the next time the controller
becomes idle.
*/
static t_stat ds_detach (UNIT *uptr)
{
t_stat result;
result = dl_detach (&mac_cntlr, uptr); /* detach the drive */
if (result == SCPE_INCOMP) { /* if the controller must be called before returning */
call_controller (NULL); /* then let it know to poll the drives */
return SCPE_OK; /* before returning with success */
}
else /* otherwise */
return result; /* return the status of the detach */
}
/* Load or unload the drive heads.
The SET DSn UNLOADED command 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. Loading fails if there is no pack in the
drive, i.e., if the unit is not attached to a disc image file.
The controller library routine handles command validation and setting the
appropriate drive unit status. It will return an error code if the command
fails. Otherwise, it will return SCPE_INCOMP if the command must be
completed with a controller call or SCPE_OK if the command is complete. If
the controller is idle, a call will be needed to poll the drives for
attention; otherwise, the drives will be polled the next time the controller
becomes idle.
*/
static t_stat ds_load_unload (UNIT *uptr, int32 value, char *cptr, void *desc)
{
const t_bool load = (value != UNIT_UNLOAD); /* TRUE if the heads are loading */
t_stat result;
result = dl_load_unload (&mac_cntlr, uptr, load); /* load or unload the heads */
if (result == SCPE_INCOMP) { /* if the controller must be called before returning */
call_controller (NULL); /* then let it know to poll the drives */
return SCPE_OK; /* before returning with success */
}
else /* otherwise */
return result; /* return the status of the load or unload */
}
/* Interface local utility routines */
/* Master reset.
A master reset is generated either by an IORESET signal or a Programmed
Master Clear (CIO with bit 0 set). It initializes the interface to its idle
state. In addition, if jumper W1 (PRESET_ENABLE) is set, it asserts the
CLEAR flag to the disc controller to perform a hard clear.
*/
static void master_reset (void)
{
interrupt_mask = SET; /* set the interrupt mask */
ds_dib.interrupt_request = CLEAR; /* clear any current */
ds_dib.interrupt_active = CLEAR; /* interrupt request */
sio_busy = CLEAR; /* clear the SIO busy */
input_xfer = CLEAR; /* input transfer */
output_xfer = CLEAR; /* and output transfer flip-flops */
data_overrun = CLEAR; /* clear the data overrun */
end_of_data = CLEAR; /* end of data */
test_mode = CLEAR; /* and test mode flip-flops */
deny_sio_busy (); /* clear the logic affected by SIO Busy */
flags &= ~XFRNG; /* clear the transfer error flag */
status_word = 0; /* clear the status register */
if (PRESET_ENABLE) { /* if jumper W1 (preset) is set to "E" */
flags |= CLEARF; /* then assert CLEAR */
call_controller (NULL); /* to the controller */
flags &= ~CLEARF; /* to do a hard clear */
}
return;
}
/* Deny SIO busy.
The internal SIO Busy signal enables a number of logic devices on the
interface associated with SIO channel transfers. When SIO Busy is denied,
those devices are set or cleared as appropriate in preparation for the next
SIO program.
*/
static void deny_sio_busy (void)
{
device_sr = CLEAR; /* clear the service request, */
jump_met = CLEAR; /* jump met, */
data_wait = CLEAR; /* and wait flip-flops */
retry_counter = 0; /* clear the retry counter */
flags = flags & ~(CMRDY | DTRDY) | INTOK | EOD; /* clear CMRDY and DTRDY and set INTOK and EOD flags */
return;
}
/* Clear interface logic.
The clear interface logic signal is asserted during channel operation either
when the interface requests an interrupt or the channel indicates a transfer
failure by asserting XFERERROR. It clears the SIO Busy, Input Transfer, and
Output Transfer flip-flops, pulses the REQ line to abort the channel program,
and sends EOD to the disc controller to abort any in-progress data transfer.
The signal is inhibited when an SIO program is not active.
*/
static void clear_interface_logic (DIB *dibptr)
{
if (sio_busy == SET) { /* if a channel program is in progress */
sio_busy = CLEAR; /* then clear the SIO busy */
input_xfer = CLEAR; /* input transfer */
output_xfer = CLEAR; /* and output transfer flip-flops */
end_of_data = SET; /* set the end of data flip-flop */
deny_sio_busy (); /* deny the SIO Busy signal */
sel_assert_REQ (dibptr); /* abort the channel program */
}
return;
}
/* Call the disc controller.
The 13037 disc controller connects to CPU interfaces via a 16-bit data bus, a
6-bit flag bus, a 4-bit function bus, and five additional control signals.
The controller continuously monitors the flag bus and reacts to the interface
changing the flag states by placing or accepting data on the data bus and
issuing commands to the interface via the function bus. The controller
supports up to eight CPU interfaces simultaneously, and provision is made to
poll each interface in turn via select and disconnect functions. An
interface only responds if it is currently selected.
In simulation, a call to the dl_controller routine informs the controller of
a (potential) change in flag state. The current set of flags and data bus
value are supplied, and the controller returns a combined set of functions
and a data bus value.
The controller must be called any time there is a change in the state of the
interface or the drive units. Generally, the cases that require notification
are when the interface:
- has a new command to execute
- has a new data word available to send
- has obtained the last data word received
- has received a unit service event notification
- has detected insertion or removal of a disc pack from a drive
- has detected loading or unloading of a drive's heads
- wants to hard-clear the controller
The set of returned functions is processed sequentially, updating the
interface state as indicated. Some functions are not used by this interface,
so they are masked off before processing to improve performance.
Disc commands may be "stacked" on the interface by asserting PCONTSTB to
store the new command into the data buffer register while the controller is
still busy with the previous command. This will assert CMRDY (if not in test
mode), but the controller will not react to this signal until it finishes the
current command. For example, a channel program containing the Wakeup and
End commands will transmit the End before the Wakeup completes. This occurs
because Wakeup asserts IFGTC to clear the command (and thereby asserts CHANSR
to allow the channel to continue) about 18 microseconds before the controller
completes the command and returns to the wait loop. In simulation, an
explicit check is made when a command completes. If a stacked command is
seen, the controller is called again to start it.
Because the disc is a synchronous device, overrun or underrun can occur if
the interface is not ready when the controller must transfer data. There are
four conditions that lead to an overrun or underrun:
1. The controller is ready with a disc read word (IFCLK * IFIN), but the
interface buffer is full (DTRDY).
2. The controller needs a disc write word (IFCLK * IFOUT), but the interface
buffer is empty (~DTRDY).
3. The CPU attempts to read a word, but the interface buffer is empty
(~DTRDY).
4. The CPU attempts to write a word, but the interface buffer is full
(DTRDY).
The hardware design of the interface prevents the last two conditions, as the
interface will assert CHANSR only when the buffer is full (read) or empty
(write). The interface does detect the first two conditions and sets the
data overrun flip-flop if either occurs.
Implementation notes:
1. In hardware, OVRUN will be asserted when the controller requests write
data when the buffer is empty. In simulation, OVRUN will not be asserted
when the controller is called with the empty buffer; instead, it will be
asserted for the next controller call. Because the controller will be
called for the intersector phase, and because OVRUN isn't checked until
that point, this "late" assertion does not affect overrun detection.
2. In hardware, the data ready flip-flop is toggled as a result of reading
or writing a word from or to the controller. We follow that practice
here, rather than setting or clearing it, which would be more
appropriate.
3. The hardware interface decodes the DSCIF and SELIF functions to allow the
controller to be shared by two or more CPUs. In simulation, these
functions are ignored, as the simulator supports only one CPU connected
to the interface.
*/
static void call_controller (UNIT *uptr)
{
CNTLR_IFN_IBUS result;
CNTLR_IFN_SET command_set;
CNTLR_IFN command;
CNTLR_FLAG_SET flag_set;
if (data_overrun == SET && (flags & XFRNG) == NO_FLAGS) /* if an overrun occurred without a transfer error */
flags |= OVRUN; /* then tell the controller */
if (test_mode == SET) /* if in test mode */
flag_set = flags & CLEARF; /* then all flags except CLEAR are inhibited */
else /* otherwise */
flag_set = flags; /* present the full set of flags to the controller */
do { /* call the controller potentially more than once */
result = dl_controller (&mac_cntlr, uptr, /* to start or continue a command */
flag_set, buffer_word);
command_set = DLIFN (result) & ~UNUSED_COMMANDS; /* strip the commands we don't use as an efficiency */
while (command_set) { /* process the set of returned interface commands */
command = DLNEXTIFN (command_set); /* isolate the next command */
switch (command) { /* dispatch an interface command */
case IFIN: /* Interface In */
if (flags & DTRDY) /* if the buffer is still full */
data_overrun = SET; /* then this input overruns it */
else { /* otherwise the buffer is empty */
device_sr = ! end_of_data; /* so request the next word unless EOT */
if ((input_xfer == CLEAR /* if not configured to read */
|| output_xfer == SET) /* or configured to write */
&& (flags & EOD) == NO_FLAGS) /* and the transfer is active */
flags |= XFRNG; /* then set the transfer is no good */
}
buffer_word = DLIBUS (result); /* store the data word in the buffer */
flags ^= DTRDY; /* and toggle (set) the data ready flag */
break;
case IFOUT: /* Interface Out */
if ((flags & DTRDY) == NO_FLAGS) /* if the buffer is empty */
data_overrun = SET; /* then this output underruns it */
if (end_of_data == SET) /* if this is the last transfer */
flags |= EOD; /* then tell the controller */
else { /* otherwise the transfer continues */
device_sr = SET; /* so request the next word */
if ((output_xfer == CLEAR /* if not configured to write */
|| input_xfer == SET) /* or configured to read */
&& (flags & EOD) == NO_FLAGS) /* and the transfer is active */
flags |= XFRNG; /* then set the transfer is no good */
}
flags ^= DTRDY; /* toggle (clear) the data ready flag */
break;
case IFGTC: /* Interface Get Command */
flags &= ~(CMRDY | DTRDY | EOD | OVRUN); /* clear the interface transfer flags */
end_of_data = CLEAR; /* clear the end-of-data */
data_overrun = CLEAR; /* and data-overrun flip-flops */
device_sr = SET; /* request channel service */
break;
case RQSRV: /* Request Service */
flags &= ~(EOD | OVRUN); /* clear the end of data and data overrun flags */
end_of_data = CLEAR; /* clear the */
data_overrun = CLEAR; /* corresponding flip-flops */
device_sr = SET; /* request channel service */
break;
case SRTRY: /* Set Retry */
retry_counter = DLIBUS (result); /* store the data value into the retry counter */
break;
case DVEND: /* Device End */
device_end = SET; /* set the device end */
jump_met = SET; /* and the "jump met condition" flip-flops */
if (retry_counter > 0) { /* if retries remain */
retry_counter = retry_counter - 1; /* then decrement the retry counter */
break; /* and try again */
}
/* otherwise, request an interrupt */
/* fall into the STINT case */
case STINT: /* Set Interrupt */
flags &= ~XFRNG; /* clear the transfer error flag */
clear_interface_logic (&ds_dib); /* clear the interface to abort the transfer */
ds_dib.interrupt_request = SET; /* set the request flip-flop */
if (interrupt_mask) /* if the interrupt mask is satisfied */
iop_assert_INTREQ (&ds_dib); /* then assert the INTREQ signal */
break;
case WRTIO: /* Write TIO */
status_word = DLIBUS (result) & ST_MASK; /* save the value without the SPD bits for TIO */
break;
case DSCIF: /* not used by this simulation */
case SELIF: /* not used by this simulation */
break;
case BUSY: /* not decoded by this interface */
case IFPRF: /* not decoded by this interface */
case FREE: /* not decoded by this interface */
case STDFL: /* not decoded by this interface */
break;
}
command_set &= ~command; /* remove the current command from the set */
} /* and continue with the remaining commands */
}
while (flags & CMRDY /* call the controller again if a command is pending */
&& result & FREE /* and a prior command just completed */
&& test_mode == CLEAR); /* and not in test mode, which inhibits CMRDY */
return;
}