These changes facilitate more robust parameter type checking and helps to identify unexpected coding errors. Most simulators can now also be compiled with a C++ compiler without warnings. Additionally, these changes have also been configured to facilitate easier backporting of simulator and device simulation modules to run under the simh v3.9+ SCP framework.
2135 lines
101 KiB
C
2135 lines
101 KiB
C
/* hp2100_di_da.c: HP 12821A HP-IB Disc Interface simulator for Amigo disc drives
|
|
|
|
Copyright (c) 2011-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.
|
|
|
|
DA 12821A Disc Interface with Amigo disc drives
|
|
|
|
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
|
|
Removed redundant global declarations
|
|
24-Oct-12 JDB Changed CNTLR_OPCODE to title case to avoid name clash
|
|
07-May-12 JDB Cancel the intersector delay if an untalk is received
|
|
29-Mar-12 JDB First release
|
|
04-Nov-11 JDB Created DA device
|
|
|
|
References:
|
|
- HP 13365 Integrated Controller Programming Guide (13365-90901, Feb-1980)
|
|
- HP 7910 Disc Drive Service Manual (07910-90903, Apr-1981)
|
|
- 12745D Disc Controller (13037) to HP-IB Adapter Kit Installation and
|
|
Service Manual (12745-90911, Sep-1983)
|
|
- HP's 5 1/4-Inch Winchester Disc Drive Service Documentation
|
|
(09134-90032, Aug-1983)
|
|
- HP 12992 Loader ROMs Installation Manual (12992-90001, Apr-1986)
|
|
- RTE Driver DVA32 Source (92084-18708, revision 2540)
|
|
- IEEE Standard Digital Interface for Programmable Instrumentation
|
|
(IEEE-488A-1980, Sep-1979)
|
|
|
|
|
|
The HP 7906H, 7920H, and 7925H Integrated Controller Disc (ICD) drives were
|
|
connected via an 12821A disc interface and provided 20MB, 50MB, and 120MB
|
|
capacities. The drives were identical to the 7906M, 7920M, and 7925M
|
|
Multi-Access Controller (MAC) units but incorporated internal two-card
|
|
controllers in each drive and connected to the CPU interface via the
|
|
Hewlett-Packard Interface Bus (HP-IB), HP's implementation of IEEE-488. Each
|
|
controller was dedicated to a single drive and operated similarly to the
|
|
12745 Disc Controller to HP-IB Adapter option for the 13037 Disc Controller
|
|
chassis. The 7906H was introduced in 1980 (there was no 7905H version, as
|
|
the 7905 was obsolete by that time). Up to four ICD drives could be
|
|
connected to a single 12821A card. The limitation was imposed by the bus
|
|
loading and the target data transfer rate.
|
|
|
|
The ICD command set essentially was the MAC command set modified for
|
|
single-unit operation. The unit number and CPU hold bit fields in the opcode
|
|
words were unused in the ICD implementation. The Load TIO Register, Wakeup,
|
|
and Request Syndrome commands were removed, as Load TIO was used with the HP
|
|
3000, Wakeup was used in a multi-CPU environment, and the simpler ICD
|
|
controller did not support ECC. Controller status values 02B (Unit
|
|
Available) and 27B (Unit Unavailable) were dropped as the controller
|
|
supported only single units, 12B (I/O Program Error) was reused to indicate
|
|
HP-IB protocol errors, 13B (Sync Not Received) was added, and 17B (Possibly
|
|
Correctable Data Error) was removed as error correction was not supported.
|
|
|
|
Some minor redefinitions also occurred. For example, status 14B (End of
|
|
Cylinder) was expanded to include an auto-seek beyond the drive limits, and
|
|
37B (Drive Attention) was restricted just head unloads from head loads and
|
|
unloads.
|
|
|
|
The command set was expanded to include several commands related to HP-IB
|
|
operation. These were, in large part, adapted from the Amigo disc command
|
|
protocol outlined in the service manual for the HP 9133/34/35 series of
|
|
5-1/4" Winchester drives. They include the Amigo Identify and Amigo Clear
|
|
sequences, Read and Write Loopback channel tests, and controller Self Test
|
|
commands.
|
|
|
|
This simulator implements the Amigo disc protocol. It calls the 12821A Disc
|
|
Interface (DI) simulator to send and receive bytes across the HP-IB to and
|
|
from the CPU, and it calls the HP Disc Library to implement the controller
|
|
functions related to disc unit operation (e.g., seek, read, write, etc.).
|
|
Four units are provided, and any combination of 7906H/20H/25H drives may be
|
|
defined.
|
|
|
|
Unfortunately, the primary reference for the ICD controller (the HP 13365
|
|
Integrated Controller Programming Guide) does not indicate parallel poll
|
|
responses for these HP-IB commands. Therefore, the responses have been
|
|
derived from the sequences in the 7910 and 12745 manuals, although they
|
|
sometimes conflict.
|
|
|
|
The drives respond to the following commands; the secondary and opcode
|
|
numeric values are in hex, and the bus addressing state is indicated by U
|
|
[untalk], L [listen], and T [talk]:
|
|
|
|
Bus Sec Op Operation
|
|
--- --- -- --------------------------------
|
|
U MSA -- Amigo Identify
|
|
|
|
L 00 -- Write Data
|
|
L 08 00 Cold Load Read
|
|
L 08 01 Recalibrate
|
|
L 08 02 Seek
|
|
L 08 03 Request Status
|
|
L 08 04 Request Sector Address
|
|
L 08 05 Read
|
|
L 08 06 Read Full Sector
|
|
L 08 07 Verify
|
|
L 08 08 Write
|
|
L 08 09 Write Full Sector
|
|
L 08 0A Clear
|
|
L 08 0B Initialize
|
|
L 08 0C Address Record
|
|
L 08 0E Read with Offset
|
|
L 08 0F Set File Mask
|
|
L 08 12 Read without Verify
|
|
L 08 14 Request Logical Disc Address
|
|
L 08 15 End
|
|
L 09 -- Cyclic Redundancy Check
|
|
L 10 -- Amigo Clear
|
|
L 1E -- Write Loopback
|
|
L 1F ss Initiate Self-Test <ss>
|
|
|
|
T 00 -- Read Data
|
|
T 08 -- Read Status
|
|
T 09 -- Cyclic Redundancy Check
|
|
T 10 -- Device Specified Jump
|
|
T 1E -- Read Loopback
|
|
T 1F -- Return Self-Test Result
|
|
|
|
In addition, the controller responds to the Selected Device Clear primary
|
|
(04).
|
|
|
|
|
|
HP-IB Transaction Sequences
|
|
===========================
|
|
|
|
Amigo Identify
|
|
|
|
ATN UNT Untalk
|
|
ATN MSA My secondary address
|
|
DAB ID data byte #1 = 00H
|
|
EOI DAB ID data byte #2 = 03H
|
|
ATN OTA Talk 30
|
|
|
|
|
|
Amigo Clear
|
|
|
|
ATN MLA My listen address
|
|
ATN SCG Secondary command 10H
|
|
ppd Parallel poll disabled
|
|
EOI DAB Unused data byte
|
|
ATN SDC Selected device clear
|
|
ATN UNL Unlisten
|
|
...
|
|
ppe Parallel poll enabled when clear completes
|
|
|
|
|
|
CRC
|
|
|
|
ATN MTA My talk address
|
|
ATN SCG Secondary command 09H
|
|
ppd Parallel poll disabled
|
|
DAB Data byte #1
|
|
...
|
|
EOI DAB Data byte #n
|
|
ppe Parallel poll enabled
|
|
ATN UNT Untalk
|
|
|
|
or
|
|
|
|
ATN MLA My listen address
|
|
ATN SCG Secondary command 09H
|
|
ppd Parallel poll disabled
|
|
DAB Data byte #1
|
|
...
|
|
EOI DAB Data byte #n
|
|
ppe Parallel poll enabled
|
|
ATN UNL Unlisten
|
|
|
|
|
|
Device Specified Jump
|
|
|
|
ATN MTA My talk address
|
|
ATN SCG Secondary command 10H
|
|
ppd Parallel poll disabled
|
|
EOI DAB DSJ data byte
|
|
ATN UNT Untalk
|
|
|
|
|
|
Initiate Self-Test and Return Self-Test Result
|
|
|
|
ATN MLA My listen address
|
|
ATN SCG Secondary command 1FH
|
|
ppd Parallel poll disabled
|
|
EOI DAB Self-test number
|
|
ppe Parallel poll enabled
|
|
ATN UNL Unlisten
|
|
|
|
ATN MTA My talk address
|
|
ATN SCG Secondary command 1FH
|
|
ppd Parallel poll disabled
|
|
EOI DAB Result data byte
|
|
ppe Parallel poll enabled
|
|
ATN UNT Untalk
|
|
|
|
|
|
Write Loopback and Read Loopback
|
|
|
|
ATN MLA My listen address
|
|
ATN SCG Secondary command 1EH
|
|
ppd Parallel poll disabled
|
|
DAB Loopback data byte #1
|
|
...
|
|
EOI DAB Loopback data byte #256
|
|
ppe Parallel poll enabled
|
|
ATN UNL Unlisten
|
|
|
|
ATN MTA My talk address
|
|
ATN SCG Secondary command 1EH
|
|
ppd Parallel poll disabled
|
|
DAB Loopback data byte #1
|
|
...
|
|
EOI DAB Loopback data byte #16
|
|
ppe Parallel poll enabled
|
|
ATN UNT Untalk
|
|
|
|
|
|
Recalibrate and Seek
|
|
|
|
ATN MLA My listen address
|
|
ATN SCG Secondary command 08H
|
|
ppd Parallel poll disabled
|
|
DAB Opcode 01H, 02H
|
|
... (one to five
|
|
EOI DAB parameter bytes)
|
|
ATN UNL Unlisten
|
|
...
|
|
ppe Parallel poll enabled when seek completes
|
|
|
|
|
|
Clear, Address Record, and Set File Mask
|
|
|
|
ATN MLA My listen address
|
|
ATN SCG Secondary command 08H
|
|
ppd Parallel poll disabled
|
|
DAB Opcode 0AH, 0CH, 0FH
|
|
... (one to five
|
|
EOI DAB parameter bytes)
|
|
ppe Parallel poll enabled
|
|
ATN UNL Unlisten
|
|
|
|
|
|
End
|
|
|
|
ATN MLA My listen address
|
|
ATN SCG Secondary command 08H
|
|
ppd Parallel poll disabled
|
|
DAB Opcode 15H
|
|
EOI DAB Unused data byte
|
|
ATN UNL Unlisten
|
|
|
|
|
|
Request Status, Request Sector Address, and Request Logical Disc Address
|
|
|
|
ATN MLA My listen address
|
|
ATN SCG Secondary command 08H
|
|
ppd Parallel poll disabled
|
|
DAB Opcode 03H, 04H, 14H
|
|
EOI DAB Unused data byte
|
|
ATN UNL Unlisten
|
|
|
|
ATN MTA My talk address
|
|
ATN SCG Secondary command 08H
|
|
DAB Status byte #1
|
|
... (two to four
|
|
EOI DAB status bytes)
|
|
ppe Parallel poll enabled
|
|
ATN UNT Untalk
|
|
|
|
|
|
Cold Load Read, Read, Read Full Sector, Verify, Read with Offset, and Read
|
|
without Verify
|
|
|
|
ATN MLA My listen address
|
|
ATN SCG Secondary command 08H
|
|
ppd Parallel poll disabled
|
|
DAB Opcode 00H, 05H, 06H, 07H, 0EH, 12H
|
|
EOI DAB Unused data byte
|
|
ATN UNL Unlisten
|
|
|
|
ATN MTA My talk address
|
|
ATN SCG Secondary command 00H
|
|
DAB Read data byte #1
|
|
...
|
|
DAB Read data byte #n
|
|
ATN UNT Untalk
|
|
...
|
|
ppe Parallel poll enabled when sector ends
|
|
|
|
|
|
Write, Write Full Sector, and Initialize
|
|
|
|
ATN MLA My listen address
|
|
ATN SCG Secondary command 08H
|
|
ppd Parallel poll disabled
|
|
DAB Opcode 08H, 09H, 0BH
|
|
EOI DAB Unused data byte
|
|
ATN UNL Unlisten
|
|
|
|
ATN MLA My listen address
|
|
ATN SCG Secondary command 00H
|
|
DAB Write data byte #1
|
|
...
|
|
EOI DAB Write data byte #n
|
|
ppe Parallel poll enabled
|
|
ATN UNL Unlisten
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The 12745 does not alter the parallel poll response for the
|
|
Device-Specified Jump command.
|
|
|
|
2. The 7910 does not perform a parallel poll response enable and disable
|
|
between the Initiate Self-Test and Return Self-Test Result commands.
|
|
|
|
3. The 12745 does not disable the parallel poll response for the Read
|
|
Loopback command.
|
|
*/
|
|
|
|
|
|
|
|
#include "hp2100_defs.h"
|
|
#include "hp2100_di.h"
|
|
#include "hp2100_disclib.h"
|
|
|
|
|
|
|
|
/* Program constants */
|
|
|
|
#define DA_UNITS 4 /* number of addressable disc units */
|
|
|
|
|
|
/* Interface states */
|
|
|
|
typedef enum {
|
|
idle = 0, /* idle = default for reset */
|
|
opcode_wait, /* waiting for opcode reception */
|
|
parameter_wait, /* waiting for parameter reception */
|
|
read_wait, /* waiting for send read data secondary */
|
|
write_wait, /* waiting for receive write data secondary */
|
|
status_wait, /* waiting for send status secondary */
|
|
command_exec, /* executing an interface command */
|
|
command_wait, /* waiting for command completion */
|
|
read_xfer, /* sending read data or status */
|
|
write_xfer, /* receiving write data */
|
|
error_source, /* sending bytes for error recovery */
|
|
error_sink /* receiving bytes for error recovery */
|
|
} IF_STATE;
|
|
|
|
|
|
/* Interface state names */
|
|
|
|
static const char *if_state_name [] = {
|
|
"idle",
|
|
"opcode wait",
|
|
"parameter wait",
|
|
"read wait",
|
|
"write wait",
|
|
"status wait",
|
|
"command execution",
|
|
"command wait",
|
|
"read transfer",
|
|
"write transfer",
|
|
"error source",
|
|
"error sink"
|
|
};
|
|
|
|
|
|
/* Next interface state after command recognition */
|
|
|
|
static const IF_STATE next_state [] = {
|
|
read_wait, /* cold load read */
|
|
command_exec, /* recalibrate */
|
|
command_exec, /* seek */
|
|
status_wait, /* request status */
|
|
status_wait, /* request sector address */
|
|
read_wait, /* read */
|
|
read_wait, /* read full sector */
|
|
command_exec, /* verify */
|
|
write_wait, /* write */
|
|
write_wait, /* write full sector */
|
|
command_exec, /* clear */
|
|
write_wait, /* initialize */
|
|
command_exec, /* address record */
|
|
idle, /* request syndrome */
|
|
read_wait, /* read with offset */
|
|
command_exec, /* set file mask */
|
|
idle, /* invalid */
|
|
idle, /* invalid */
|
|
read_wait, /* read without verify */
|
|
idle, /* load TIO register */
|
|
status_wait, /* request disc address */
|
|
command_exec, /* end */
|
|
idle /* wakeup */
|
|
};
|
|
|
|
|
|
/* Interface commands */
|
|
|
|
typedef enum {
|
|
invalid = 0, /* invalid = default for reset */
|
|
disc_command, /* MLA 08 */
|
|
crc_listen, /* MLA 09 */
|
|
amigo_clear, /* MLA 10 */
|
|
write_loopback, /* MLA 1E */
|
|
initiate_self_test, /* MLA 1F */
|
|
crc_talk, /* MTA 09 */
|
|
device_specified_jump, /* MTA 10 */
|
|
read_loopback, /* MTA 1E */
|
|
return_self_test_result, /* MTA 1F */
|
|
amigo_identify /* UNT MSA */
|
|
} IF_COMMAND;
|
|
|
|
/* Interface command names */
|
|
|
|
static const char *if_command_name [] = {
|
|
"invalid",
|
|
"disc command",
|
|
"CRC listen",
|
|
"Amigo clear",
|
|
"write loopback",
|
|
"initiate self-test",
|
|
"CRC talk",
|
|
"device specified jump",
|
|
"read loopback",
|
|
"return self-test result",
|
|
"Amigo identify"
|
|
};
|
|
|
|
|
|
|
|
/* Amigo disc state variables */
|
|
|
|
static uint16 buffer [DL_BUFSIZE]; /* command/status/sector buffer */
|
|
|
|
static uint8 if_dsj [DA_UNITS]; /* ICD controller DSJ values */
|
|
static IF_STATE if_state [DA_UNITS]; /* ICD controller state */
|
|
static IF_COMMAND if_command [DA_UNITS]; /* ICD controller command */
|
|
|
|
static CNTLR_VARS icd_cntlr [DA_UNITS] = /* ICD controllers: */
|
|
{ { CNTLR_INIT (ICD, buffer, NULL) }, /* unit 0 controller */
|
|
{ CNTLR_INIT (ICD, buffer, NULL) }, /* unit 1 controller */
|
|
{ CNTLR_INIT (ICD, buffer, NULL) }, /* unit 2 controller */
|
|
{ CNTLR_INIT (ICD, buffer, NULL) } }; /* unit 3 controller */
|
|
|
|
|
|
|
|
/* Amigo disc global VM routines */
|
|
|
|
t_stat da_reset (DEVICE *dptr);
|
|
t_stat da_attach (UNIT *uptr, CONST char *cptr);
|
|
t_stat da_detach (UNIT *uptr);
|
|
|
|
/* Amigo disc global SCP routines */
|
|
|
|
t_stat da_load_unload (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
|
|
|
|
/* Amigo disc local utility routines */
|
|
|
|
static t_bool start_command (uint32 unit);
|
|
static void abort_command (uint32 unit, CNTLR_STATUS status, IF_STATE state);
|
|
static void complete_read (uint32 unit);
|
|
static void complete_write (uint32 unit);
|
|
static void complete_abort (uint32 unit);
|
|
static uint8 get_buffer_byte (CVPTR cvptr);
|
|
static void put_buffer_byte (CVPTR cvptr, uint8 data);
|
|
static t_stat activate_unit (UNIT *uptr);
|
|
|
|
|
|
|
|
/* Amigo disc VM global data structures.
|
|
|
|
da_dib DA device information block
|
|
da_unit DA unit list
|
|
da_reg DA register list
|
|
da_mod DA modifier list
|
|
da_dev DA device descriptor
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The IFSTAT and IFCMD registers are declared to accommodate the
|
|
corresponding arrays of enums. Arrayed registers assume that elements
|
|
are allocated space only to the integral number of bytes implied by the
|
|
"width" field. The storage size of an enum is implementation-defined, so
|
|
we must determine the number of bits for "width" at compile time.
|
|
PV_LEFT is used to avoid the large number of leading zeros that would be
|
|
displayed if an implementation stored enums in full words.
|
|
|
|
2. The CNVARS register is included to ensure that the controller state
|
|
variables array is saved by a SAVE command. It is declared as a hidden,
|
|
read-only byte array of a depth compatible with the size of the array.
|
|
|
|
There does not appear to be a way to expose the fields of the four
|
|
controller state variables as arrayed registers. Access to an array
|
|
always assumes that elements appear at memory offsets equal to the
|
|
element size, i.e., a 32-bit arrayed register has elements at four-byte
|
|
offsets. There's no way to specify an array of structure elements where
|
|
a given 32-bit field appears at, say, 92-byte offsets (i.e., the size of
|
|
the structure).
|
|
*/
|
|
|
|
DEVICE da_dev;
|
|
|
|
DIB da_dib = { &di_io, DI_DA, da };
|
|
|
|
#define UNIT_FLAGS (UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | UNIT_DISABLE | UNIT_UNLOAD)
|
|
|
|
UNIT da_unit [] = {
|
|
{ UDATA (&da_service, UNIT_FLAGS | MODEL_7906 | SET_BUSADR (0), D7906_WORDS) }, /* drive unit 0 */
|
|
{ UDATA (&da_service, UNIT_FLAGS | MODEL_7906 | SET_BUSADR (1), D7906_WORDS) }, /* drive unit 1 */
|
|
{ UDATA (&da_service, UNIT_FLAGS | MODEL_7906 | SET_BUSADR (2), D7906_WORDS) }, /* drive unit 2 */
|
|
{ UDATA (&da_service, UNIT_FLAGS | MODEL_7906 | SET_BUSADR (3), D7906_WORDS) } /* drive unit 3 */
|
|
};
|
|
|
|
REG da_reg [] = {
|
|
DI_REGS (da),
|
|
|
|
{ BRDATA (BUFFER, buffer, 8, 16, DL_BUFSIZE) },
|
|
|
|
{ BRDATA (DSJ, if_dsj, 10, 2, DA_UNITS) },
|
|
{ BRDATA (ISTATE, if_state, 10, sizeof (IF_STATE) * CHAR_BIT, DA_UNITS), PV_LEFT },
|
|
{ BRDATA (ICMD, if_command, 10, sizeof (IF_COMMAND) * CHAR_BIT, DA_UNITS), PV_LEFT },
|
|
|
|
{ BRDATA (CNVARS, icd_cntlr, 10, CHAR_BIT, sizeof (CNTLR_VARS) * DA_UNITS), REG_HRO },
|
|
|
|
{ NULL }
|
|
};
|
|
|
|
MTAB da_mod [] = {
|
|
DI_MODS (da_dev),
|
|
|
|
{ UNIT_UNLOAD, UNIT_UNLOAD, "heads unloaded", "UNLOADED", &da_load_unload, NULL, NULL },
|
|
{ UNIT_UNLOAD, 0, "heads loaded", "LOADED", &da_load_unload, NULL, NULL },
|
|
|
|
{ UNIT_WLK, UNIT_WLK, "write locked", "LOCKED", NULL, NULL, NULL },
|
|
{ UNIT_WLK, 0, "write enabled", "WRITEENABLED", NULL, NULL, NULL },
|
|
|
|
{ UNIT_FMT, UNIT_FMT, "format enabled", "FORMAT", NULL, NULL, NULL },
|
|
{ UNIT_FMT, 0, "format disabled", "NOFORMAT", NULL, NULL, NULL },
|
|
|
|
{ UNIT_MODEL, MODEL_7906, "7906H", "7906H", &dl_set_model, NULL, NULL },
|
|
{ UNIT_MODEL, MODEL_7920, "7920H", "7920H", &dl_set_model, NULL, NULL },
|
|
{ UNIT_MODEL, MODEL_7925, "7925H", "7925H", &dl_set_model, NULL, NULL },
|
|
|
|
{ 0 }
|
|
};
|
|
|
|
DEVICE da_dev = {
|
|
"DA", /* device name */
|
|
da_unit, /* unit array */
|
|
da_reg, /* register array */
|
|
da_mod, /* modifier array */
|
|
DA_UNITS, /* number of units */
|
|
10, /* address radix */
|
|
26, /* address width */
|
|
1, /* address increment */
|
|
8, /* data radix */
|
|
16, /* data width */
|
|
NULL, /* examine routine */
|
|
NULL, /* deposit routine */
|
|
&da_reset, /* reset routine */
|
|
&da_boot, /* boot routine */
|
|
&da_attach, /* attach routine */
|
|
&da_detach, /* detach routine */
|
|
&da_dib, /* device information block */
|
|
DEV_DEBUG | DEV_DISABLE, /* device flags */
|
|
0, /* debug control flags */
|
|
di_deb, /* debug flag name table */
|
|
NULL, /* memory size change routine */
|
|
NULL /* logical device name */
|
|
};
|
|
|
|
|
|
|
|
/* Amigo disc global VM routines */
|
|
|
|
|
|
/* Service an Amigo disc drive I/O event.
|
|
|
|
The service routine is called to execute commands and control the transfer of
|
|
data to and from the HP-IB card. The actions to be taken depend on the
|
|
current state of the ICD interface. The possibilities are:
|
|
|
|
1. A command is pending on the interface. This occurs only when a command
|
|
is received while a Seek or Recalibrate command is in progress.
|
|
|
|
2. A command is executing.
|
|
|
|
3. Data is being sent or received over the HP-IB during command execution.
|
|
|
|
4. Dummy bytes are being sent or received over the HP-IB due to a command
|
|
error.
|
|
|
|
Entry to the the service routine in any other interface state or to process a
|
|
command not allowed in a valid state will return an Internal Error to cause a
|
|
simulator stop. Exit from the routine will be either in one of the above
|
|
states, or in the idle state if the operation is complete.
|
|
|
|
The specific actions taken for the various interface states are as follows:
|
|
|
|
command_wait
|
|
============
|
|
|
|
We are entered in this state only if a unit that was busy (still seeking)
|
|
was addressed to listen or talk. The card has been held off by asserting
|
|
NRFD after receiving MLA or MTA. Upon entry, we complete the seek and then
|
|
release the interface by denying NRFD to allow the remainder of the command
|
|
sequence to be received from the card.
|
|
|
|
command_exec
|
|
============
|
|
|
|
We are entered in this state to initiate, continue, or complete a command.
|
|
The command may be a disc command, such as Seek or Read, or an interface
|
|
command, such as Amigo Identify or Device-Specified Jump.
|
|
|
|
Disc commands call the disc library service routine to perform all of the
|
|
common controller actions. Any ICD-specific actions needed, such as
|
|
setting the DSJ value, are performed after the call.
|
|
|
|
Certain disc commands require multiple execution phases. For example, the
|
|
Read command has a start phase that reads data from the disc image file
|
|
into the sector buffer, a data phase that transfers bytes from the buffer
|
|
to the card, and an end phase that schedules the intersector gap time and
|
|
resets to the start phase. Data phase transfers are performed in the
|
|
read_xfer or write_xfer interface states.
|
|
|
|
The results of the disc library service are inferred by the controller
|
|
state. If the controller is busy, then the command continues in a new
|
|
phase. Otherwise, the command either has completed normally or has
|
|
terminated with an error. If an error has occurred during a disc command
|
|
that transfers data, DSJ is set to 1, and the interface state is changed to
|
|
source or sink dummy bytes to complete the command sequence.
|
|
|
|
Interface commands may either complete immediately (e.g., Amigo Clear) or
|
|
transfer data (e.g., DSJ).
|
|
|
|
read_xfer
|
|
=========
|
|
|
|
Commands that send data to the CPU enter the service routine to source a
|
|
byte to the bus. Bytes are transferred only when ATN and NRFD are denied;
|
|
if they are not, we simply exit, as we will be rescheduled when the lines
|
|
are dropped. Otherwise, we get a byte from the sector buffer and send it
|
|
to the card. If the card has stopped listening, or the buffer is now
|
|
empty, then we terminate the transfer and move to the end phase of the
|
|
command. Otherwise, we reschedule the next data phase byte transfer.
|
|
|
|
Disc and interface commands are handled separately, as EOI is always
|
|
asserted on the last byte of an interface command transfer and never on a
|
|
(good) disc command transfer.
|
|
|
|
write_xfer
|
|
==========
|
|
|
|
Commands that receive data from the CPU enter the service routine to
|
|
determine whether or not to continue the transfer. Our bus accept routine
|
|
has already stored the received byte in the sector buffer and has asserted
|
|
NRFD to hold off the card. If the buffer is now full, or the byte was
|
|
tagged with EOI, then we terminate the transfer and move to the end phase
|
|
of the command. Otherwise, we deny NRFD and exit; we will be rescheduled
|
|
when the next byte arrives.
|
|
|
|
error_source
|
|
============
|
|
|
|
If an error occurred during the data transfer phase of a read or status
|
|
command, a dummy byte tagged with EOI is sourced to the bus. This allows
|
|
the OS driver for the card to terminate the command and request the
|
|
controller's status.
|
|
|
|
error_sink
|
|
==========
|
|
|
|
If an error occurred during the data transfer phase of a write command,
|
|
dummy bytes are sunk from the bus until EOI is seen or the card is
|
|
unaddressed. This allows the OS driver to complete the command as expected
|
|
and then determine the cause of the failure by requesting the controller's
|
|
status.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The disc library sets the controller state to idle for a normal End,
|
|
Seek, or Recalibrate command and to wait for all other commands that end
|
|
normally. So we determine command completion by checking if the
|
|
controller is not busy, rather than checking if the controller is idle.
|
|
|
|
Drive Attention status is the normal result of the completion of a Seek
|
|
or Recalibrate command. Normal Completion status is the normal result of
|
|
all other commands.
|
|
|
|
2. The disc library returns the buffer length in words. We double the
|
|
return value to count bytes.
|
|
|
|
3. Some commands, such as DSJ, could be completed in the bus accept routine.
|
|
They are serviced here instead to avoid presenting a zero execution time
|
|
to the CPU.
|
|
|
|
4. The Amigo command set does not provide the disc with the number of bytes
|
|
that will be read, and the unit expects to be untalked when the read is
|
|
to terminate. The RTE ICD bootstrap extension does not do this.
|
|
Instead, it resets the card via CLC 0,C to terminate the Cold Load Read
|
|
that was started by the ICD boot loader ROM.
|
|
|
|
In hardware, if the LSTN control bit is cleared, e.g., by CRS,
|
|
transmission stops because the card denies NDAC and NRFD (the HP-IB
|
|
handshake requires NDAC and NRFD to be asserted to start the handshake
|
|
sequence; TACS * SDYS * ~NDAC * ~NRFD is an error condition). In
|
|
simulation, we handle this by terminating a read transfer if the card
|
|
stops accepting. If we did not, then the disc would continue to source
|
|
bytes to the bus, overflowing the card FIFO (a FIFO full condition cannot
|
|
assert NRFD if the LSTN control bit is clear).
|
|
*/
|
|
|
|
t_stat da_service (UNIT *uptr)
|
|
{
|
|
uint8 data;
|
|
CNTLR_CLASS command_class;
|
|
const int32 unit = uptr - da_unit; /* get the disc unit number */
|
|
const CVPTR cvptr = &icd_cntlr [unit]; /* get a pointer to the controller */
|
|
t_stat result = SCPE_OK;
|
|
t_bool release_interface = FALSE;
|
|
|
|
switch (if_state [unit]) { /* dispatch the interface state */
|
|
|
|
case command_wait: /* command is waiting */
|
|
release_interface = TRUE; /* release the interface at then end if it's idle */
|
|
|
|
/* fall into the command_exec handler to process the current command */
|
|
|
|
case command_exec: /* command is executing */
|
|
switch (if_command [unit]) { /* dispatch the interface command */
|
|
|
|
case disc_command: /* execute a disc command */
|
|
result = dl_service_drive (cvptr, uptr); /* service the disc unit */
|
|
|
|
if (cvptr->opcode == Clear) /* is this a Clear command? */
|
|
if_dsj [unit] = 2; /* indicate that the self test is complete */
|
|
|
|
if (cvptr->state != cntlr_busy) { /* has the controller stopped? */
|
|
if_state [unit] = idle; /* idle the interface */
|
|
|
|
if (cvptr->status == normal_completion || /* do we have normal completion */
|
|
cvptr->status == drive_attention) /* or drive attention? */
|
|
break; /* we're done */
|
|
|
|
else { /* if the status is abnormal */
|
|
if_dsj [unit] = 1; /* an error has occurred */
|
|
|
|
command_class = dl_classify (*cvptr); /* classify the command */
|
|
|
|
if (command_class == class_write) { /* did a write command fail? */
|
|
if_state [unit] = error_sink; /* sink the remaining bytes */
|
|
uptr->wait = cvptr->cmd_time; /* activate to complete processing */
|
|
}
|
|
|
|
else if (command_class != class_control) { /* did a read or status command fail? */
|
|
if_state [unit] = error_source; /* source an error byte */
|
|
uptr->wait = cvptr->cmd_time; /* activate to complete processing */
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (uptr->PHASE == data_phase) { /* are we starting the data phase? */
|
|
cvptr->length = cvptr->length * 2; /* convert the buffer length to bytes */
|
|
|
|
if (dl_classify (*cvptr) == class_write) /* is this a write command? */
|
|
if_state [unit] = write_xfer; /* set for a write data transfer */
|
|
else /* it is a read or status command */
|
|
if_state [unit] = read_xfer; /* set for a read data transfer */
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
case amigo_identify: /* Amigo Identify */
|
|
buffer [0] = 0x0003; /* store the response in the buffer */
|
|
cvptr->length = 2; /* return two bytes */
|
|
|
|
if_state [unit] = read_xfer; /* we are ready to transfer the data */
|
|
uptr->wait = cvptr->cmd_time; /* schedule the transfer */
|
|
|
|
if (DEBUG_PRI (da_dev, DEB_RWSC))
|
|
fprintf (sim_deb, ">>DA rwsc: Unit %d Amigo identify response %04XH\n",
|
|
unit, buffer [0]);
|
|
break;
|
|
|
|
|
|
case initiate_self_test: /* Initiate a self test */
|
|
sim_cancel (&da_unit [unit]); /* cancel any operation in progress */
|
|
dl_clear_controller (cvptr, /* hard-clear the controller */
|
|
&da_unit [unit],
|
|
hard_clear);
|
|
if_dsj [unit] = 2; /* set DSJ for self test completion */
|
|
if_state [unit] = idle; /* the command is complete */
|
|
di_poll_response (da, unit, SET); /* with PPR enabled */
|
|
break;
|
|
|
|
|
|
case amigo_clear: /* Amigo clear */
|
|
dl_idle_controller (cvptr); /* idle the controller */
|
|
if_dsj [unit] = 0; /* clear the DSJ value */
|
|
if_state [unit] = idle; /* the command is complete */
|
|
di_poll_response (da, unit, SET); /* with PPR enabled */
|
|
break;
|
|
|
|
|
|
default: /* no other commands are executed */
|
|
result = SCPE_IERR; /* signal an internal error */
|
|
break;
|
|
} /* end of command dispatch */
|
|
break;
|
|
|
|
|
|
case error_source: /* send data after an error */
|
|
if (! (di [da].bus_cntl & (BUS_ATN | BUS_NRFD))) { /* is the card ready for data? */
|
|
di [da].bus_cntl |= BUS_EOI; /* set EOI */
|
|
di_bus_source (da, 0); /* and send a dummy byte to the card */
|
|
if_state [unit] = idle; /* the command is complete */
|
|
}
|
|
break;
|
|
|
|
|
|
case read_xfer: /* send read data */
|
|
if (! (di [da].bus_cntl & (BUS_ATN | BUS_NRFD))) /* is the card ready for data? */
|
|
switch (if_command [unit]) { /* dispatch the interface command */
|
|
|
|
case disc_command: /* disc read or status commands */
|
|
data = get_buffer_byte (cvptr); /* get the next byte from the buffer */
|
|
|
|
if (di_bus_source (da, data) == FALSE) /* send the byte to the card; is it listening? */
|
|
cvptr->eod = SET; /* no, so terminate the read */
|
|
|
|
if (cvptr->length == 0 || cvptr->eod == SET) { /* is the data phase complete? */
|
|
uptr->PHASE = end_phase; /* set the end phase */
|
|
|
|
if (cvptr->opcode == Request_Status) /* is it a Request Status command? */
|
|
if_dsj [unit] = 0; /* clear the DSJ value */
|
|
|
|
if_state [unit] = command_exec; /* set to execute the command */
|
|
uptr->wait = cvptr->cmd_time; /* and reschedule the service */
|
|
}
|
|
|
|
else /* the data phase continues */
|
|
uptr->wait = cvptr->data_time; /* reschedule the next transfer */
|
|
|
|
break;
|
|
|
|
|
|
case amigo_identify:
|
|
case read_loopback:
|
|
case return_self_test_result:
|
|
data = get_buffer_byte (cvptr); /* get the next byte from the buffer */
|
|
|
|
if (cvptr->length == 0) /* is the transfer complete? */
|
|
di [da].bus_cntl |= BUS_EOI; /* set EOI */
|
|
|
|
if (di_bus_source (da, data) /* send the byte to the card; is it listening? */
|
|
&& cvptr->length > 0) /* and is there more to transfer? */
|
|
uptr->wait = cvptr->data_time; /* reschedule the next transfer */
|
|
|
|
else { /* the transfer is complete */
|
|
if_state [unit] = idle; /* the command is complete */
|
|
di_poll_response (da, unit, SET); /* enable the PPR */
|
|
}
|
|
break;
|
|
|
|
|
|
case device_specified_jump:
|
|
di [da].bus_cntl |= BUS_EOI; /* set EOI */
|
|
di_bus_source (da, if_dsj [unit]); /* send the DSJ value to the card */
|
|
if_state [unit] = idle; /* the command is complete */
|
|
break;
|
|
|
|
|
|
case crc_talk:
|
|
di [da].bus_cntl |= BUS_EOI; /* set EOI */
|
|
di_bus_source (da, 0); /* send dummy bytes */
|
|
break; /* until the card untalks */
|
|
|
|
|
|
default: /* no other commands send data */
|
|
result = SCPE_IERR; /* signal an internal error */
|
|
break;
|
|
} /* end of read data transfer dispatch */
|
|
break;
|
|
|
|
|
|
case error_sink: /* absorb data after an error */
|
|
cvptr->index = 0; /* absorb data until EOI asserts */
|
|
|
|
if (cvptr->eod == SET) /* is the transfer complete? */
|
|
if_state [unit] = idle; /* the command is complete */
|
|
|
|
di_bus_control (da, unit, 0, BUS_NRFD); /* deny NRFD to allow the card to resume */
|
|
break;
|
|
|
|
|
|
case write_xfer: /* receive write data */
|
|
switch (if_command [unit]) { /* dispatch the interface command */
|
|
|
|
case disc_command: /* disc write commands */
|
|
if (cvptr->length == 0 || cvptr->eod == SET) { /* is the data phase complete? */
|
|
uptr->PHASE = end_phase; /* set the end phase */
|
|
|
|
if_state [unit] = command_exec; /* set to execute the command */
|
|
uptr->wait = cvptr->cmd_time; /* and schedule the service */
|
|
|
|
if (cvptr->eod == CLEAR) /* is the transfer continuing? */
|
|
break; /* do not deny NRFD until next service! */
|
|
}
|
|
|
|
di_bus_control (da, unit, 0, BUS_NRFD); /* deny NRFD to allow the card to resume */
|
|
break;
|
|
|
|
|
|
case write_loopback:
|
|
if (cvptr->eod == SET) { /* is the transfer complete? */
|
|
cvptr->length = 16 - cvptr->length; /* set the count of bytes transferred */
|
|
if_state [unit] = idle; /* the command is complete */
|
|
}
|
|
|
|
di_bus_control (da, unit, 0, BUS_NRFD); /* deny NRFD to allow the card to resume */
|
|
break;
|
|
|
|
|
|
default: /* no other commands receive data */
|
|
result = SCPE_IERR; /* signal an internal error */
|
|
break;
|
|
} /* end of write data transfer dispatch */
|
|
break;
|
|
|
|
|
|
default: /* no other states schedule service */
|
|
result = SCPE_IERR; /* signal an internal error */
|
|
break;
|
|
} /* end of interface state dispatch */
|
|
|
|
|
|
if (uptr->wait) /* is service requested? */
|
|
activate_unit (uptr); /* schedule the next event */
|
|
|
|
if (result == SCPE_IERR && DEBUG_PRI (da_dev, DEB_RWSC)) { /* did an internal error occur? */
|
|
fprintf (sim_deb, ">>DA rwsc: Unit %d ", unit); /* report it if debugging */
|
|
|
|
if (if_state [unit] == command_exec
|
|
&& if_command [unit] == disc_command)
|
|
fprintf (sim_deb, "%s command %s phase ",
|
|
dl_opcode_name (ICD, (CNTLR_OPCODE) uptr->OP),
|
|
dl_phase_name ((CNTLR_PHASE) uptr->PHASE));
|
|
else
|
|
fprintf (sim_deb, "%s state %s ",
|
|
if_command_name [if_command [unit]],
|
|
if_state_name [if_state [unit]]);
|
|
|
|
fputs ("service not handled\n", sim_deb);
|
|
}
|
|
|
|
if (if_state [unit] == idle) { /* is the command now complete? */
|
|
if (if_command [unit] == disc_command) { /* did a disc command complete? */
|
|
if (cvptr->opcode != End) /* yes; if the command was not End, */
|
|
di_poll_response (da, unit, SET); /* then enable PPR */
|
|
|
|
if (DEBUG_PRI (da_dev, DEB_RWSC))
|
|
fprintf (sim_deb, ">>DA rwsc: Unit %d %s disc command completed\n",
|
|
unit, dl_opcode_name (ICD, cvptr->opcode));
|
|
}
|
|
|
|
else /* an interface command completed */
|
|
if (DEBUG_PRI (da_dev, DEB_RWSC))
|
|
fprintf (sim_deb, ">>DA rwsc: Unit %d %s command completed\n",
|
|
unit, if_command_name [if_command [unit]]);
|
|
|
|
if (release_interface) /* if the next command is already pending */
|
|
di_bus_control (da, unit, 0, BUS_NRFD); /* deny NRFD to allow the card to resume */
|
|
}
|
|
|
|
return result; /* return the result of the service */
|
|
}
|
|
|
|
|
|
/* Reset or preset the simulator.
|
|
|
|
In hardware, a self-test is performed by the controller at power-on. When
|
|
the self-test completes, the controller sets DSJ = 2 and enables the parallel
|
|
poll response.
|
|
|
|
A front panel PRESET or programmed CRS has no direct effect on the controller
|
|
or drive. However, the card reacts to CRS by clearing its talker and
|
|
listener states, so an in-progress read or status command will abort when the
|
|
next byte sourced to the bus finds no acceptors.
|
|
*/
|
|
|
|
t_stat da_reset (DEVICE *dptr)
|
|
{
|
|
uint32 unit;
|
|
t_stat status;
|
|
|
|
status = di_reset (dptr); /* reset the card */
|
|
|
|
if (status == SCPE_OK && (sim_switches & SWMASK ('P'))) /* is the card OK and is this a power-on reset? */
|
|
for (unit = 0; unit < dptr->numunits; unit++) { /* loop through the units */
|
|
sim_cancel (dptr->units + unit); /* cancel any current activation */
|
|
dptr->units [unit].CYL = 0; /* reset the head position */
|
|
dptr->units [unit].pos = 0; /* to cylinder 0 */
|
|
|
|
dl_clear_controller (&icd_cntlr [unit], /* hard-clear the controller */
|
|
dptr->units + unit,
|
|
hard_clear);
|
|
|
|
if_state [unit] = idle; /* reset the interface state */
|
|
if_command [unit] = invalid; /* reset the interface command */
|
|
|
|
if_dsj [unit] = 2; /* set the DSJ for power up complete */
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/* Attach a unit to a disc image file.
|
|
|
|
The simulator considers an attached unit to be connected to the bus and an
|
|
unattached unit to be disconnected, so we set the card's acceptor bit for the
|
|
selected unit if the attach is successful. An attached unit is ready if the
|
|
heads are loaded or not ready if not.
|
|
|
|
This model is slightly different than the MAC (DS) simulation, where an
|
|
unattached unit is considered "connected but not ready" -- the same
|
|
indication returned by an attached unit whose heads are unloaded. Therefore,
|
|
the situation when the simulator is started is that all DS units are
|
|
"connected to the controller but not ready," whereas all DA units are "not
|
|
connected to the bus." This eliminates the overhead of sending HP-IB
|
|
messages to unused units.
|
|
|
|
In tabular form, the simulator responses are:
|
|
|
|
Enabled Loaded Attached DS (MAC) DA (ICD)
|
|
------- ------ -------- ------------ ------------
|
|
N N N disconnected disconnected
|
|
N N Y -- --
|
|
N Y N -- --
|
|
N Y Y -- --
|
|
Y N N unloaded disconnected
|
|
Y N Y unloaded unloaded
|
|
Y Y N -- --
|
|
Y Y Y ready ready
|
|
|
|
The unspecified responses are illegal conditions; for example, the simulator
|
|
does not allow an attached unit to be disabled.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. To conform exactly to the MAC responses would have required intercepting
|
|
the SET <unit> DISABLED/ENABLED commands in order to clear or set the unit
|
|
accepting bits. However, short of intercepting the all SET commands with
|
|
a custom command table, there is no way to ensure that unit enables are
|
|
observed. Adding ENABLED and DISABLED to the modifiers table and
|
|
specifying a validation routine works for the DISABLED case but not the
|
|
ENABLED case -- set_unit_enbdis returns SCPE_UDIS before calling the
|
|
validation routine.
|
|
*/
|
|
|
|
t_stat da_attach (UNIT *uptr, CONST char *cptr)
|
|
{
|
|
t_stat result;
|
|
const int32 unit = uptr - da_unit; /* calculate the unit number */
|
|
|
|
result = dl_attach (&icd_cntlr [unit], uptr, cptr); /* attach the drive */
|
|
|
|
if (result == SCPE_OK) /* was the attach successful? */
|
|
di [da].acceptors |= (1 << unit); /* set the unit's accepting bit */
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Detach a disc image file from a unit.
|
|
|
|
As explained above, detaching a unit is the hardware equivalent of
|
|
disconnecting the drive from the bus, so we clear the unit's acceptor bit if
|
|
the detach is successful.
|
|
*/
|
|
|
|
t_stat da_detach (UNIT *uptr)
|
|
{
|
|
t_stat result;
|
|
const int32 unit = uptr - da_unit; /* calculate the unit number */
|
|
|
|
result = dl_detach (&icd_cntlr [unit], uptr); /* detach the drive */
|
|
|
|
if (result == SCPE_OK) { /* was the detach successful? */
|
|
di [da].acceptors &= ~(1 << unit); /* clear the unit's accepting bit */
|
|
di_poll_response (da, unit, CLEAR); /* and its PPR, as it's no longer present */
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Boot an Amigo disc drive.
|
|
|
|
The ICD disc bootstrap program is loaded from the HP 12992H 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 the disc at bus address 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.
|
|
*/
|
|
|
|
static const BOOT_ROM da_rom = {
|
|
0102501, /* START LIA 1 GET SWITCH REGISTER SETTING */
|
|
0100044, /* LSL 4 SHIFT A LEFT 4 */
|
|
0006111, /* CLE,SLB,RSS SR BIT 12 SET FOR MANUAL BOOT? */
|
|
0100041, /* LSL 1 NO. SHIFT HEAD # FOR RPL BOOT */
|
|
0001424, /* ALR,ALR SHIFT HEAD 2, CLEAR SIGN */
|
|
0033744, /* IOR HDSEC SET EOI BIT */
|
|
0073744, /* STA HDSEC PLACE IN COMMAND BUFFER */
|
|
0017756, /* JSB BTCTL SEND DUMMY,U-CLR,PP */
|
|
0102510, /* LIA IBI READ INPUT REGISTER */
|
|
0101027, /* ASR 7 SHIFT DRIVE 0 RESPONSE TO LSB */
|
|
0002011, /* SLA,RSS DID DRIVE 0 RESPOND? */
|
|
0027710, /* JMP *-3 NO, GO LOOK AGAIN */
|
|
0107700, /* CLC 0,C */
|
|
0017756, /* JSB BTCTL SEND TALK, CL-RD,BUS HOLDER */
|
|
0002300, /* CCE */
|
|
0017756, /* JSB BTCTL TELL CARD TO LISTEN */
|
|
0063776, /* LDA DMACW LOAD DMA CONTROL WORD */
|
|
0102606, /* OTA 6 OUTPUT TO DCPC */
|
|
0106702, /* CLC 2 READY DCPC */
|
|
0063735, /* LDA ADDR1 LOAD DMA BUFFER ADDRESS */
|
|
0102602, /* OTA 2 OUTPUT TO DCPC */
|
|
0063740, /* LDA DMAWC LOAD DMA WORD COUNT */
|
|
0102702, /* STC 2 READY DCPC */
|
|
0102602, /* OTA 2 OUTPUT TO DCPC */
|
|
0103706, /* STC 6,C START DCPC */
|
|
0102206, /* TEST SFC 6 SKIP IF DMA NOT DONE */
|
|
0117750, /* JSB ADDR2,I SUCCESSFUL END OF TRANSFER */
|
|
0102310, /* SFS IBI SKIP IF DISC ABORTED TRANSFER */
|
|
0027731, /* JMP TEST RECHECK FOR TRANSFER END */
|
|
0102011, /* ADDR1 HLT 11B ERROR HALT */
|
|
0000677, /* UNCLR OCT 677 UNLISTEN */
|
|
0000737, /* OCT 737 UNTALK */
|
|
0176624, /* DMAWC OCT 176624 UNIVERSAL CLEAR,LBO */
|
|
0000440, /* LIST OCT 440 LISTEN BUS ADDRESS 0 */
|
|
0000550, /* CMSEC OCT 550 SECONDARY GET COMMAND */
|
|
0000000, /* BOOT OCT 0 COLD LOAD READ COMMAND */
|
|
0001000, /* HDSEC OCT 1000 HEAD,SECTOR PLUS EOI */
|
|
0000677, /* UNLST OCT 677 ATN,PRIMARY UNLISTEN,PARITY */
|
|
0000500, /* TALK OCT 500 SEND READ DATA */
|
|
0100740, /* RDSEC OCT 100740 SECONDARY READ DATA */
|
|
0102055, /* ADDR2 OCT 102055 BOOT EXTENSION STARTING ADDRESS */
|
|
0004003, /* CTLP OCT 4003 INT=LBO,T,CIC */
|
|
0000047, /* OCT 47 PPE,L,T,CIC */
|
|
0004003, /* OCT 4003 INT=LBO,T,CIC */
|
|
0000413, /* OCT 413 ATN,P,L,CIC */
|
|
0001015, /* OCT 1015 INT=EOI,P,L,CIC */
|
|
0000000, /* BTCTL NOP */
|
|
0107710, /* CLC IBI,C RESET IBI */
|
|
0063751, /* BM LDA CTLP LOAD CONTROL WORD */
|
|
0102610, /* OTA IBI OUTPUT TO CONTROL REGISTER */
|
|
0102710, /* STC IBI RETURN IBI TO DATA MODE */
|
|
0037760, /* ISZ BM INCREMENT CONTROL WORD POINTER */
|
|
0002240, /* SEZ,CME */
|
|
0127756, /* JMP BTCTL,I RETURN */
|
|
0063736, /* LABL LDA UNCLR LOAD DATA WORD */
|
|
0037766, /* ISZ LABL INCREMENT WORD POINTER */
|
|
0102610, /* OTA IBI OUTPUT TO HPIB */
|
|
0002021, /* SSA,RSS SKIP IF LAST WORD */
|
|
0027766, /* JMP LABL GO BACK FOR NEXT WORD */
|
|
0102310, /* SFS IBI SKIP IF LAST WORD SENT TO BUS */
|
|
0027773, /* JMP *-1 RECHECK ACCEPTANCE */
|
|
0027757, /* JMP BTCTL+1 */
|
|
0000010, /* DMACW ABS IBI */
|
|
0170100 /* ABS -START */
|
|
};
|
|
|
|
t_stat da_boot (int32 unitno, DEVICE *dptr)
|
|
{
|
|
if (GET_BUSADR (da_unit [unitno].flags) != 0) /* booting is supported on bus address 0 only */
|
|
return SCPE_NOFNC; /* report "Command not allowed" if attempted */
|
|
|
|
if (ibl_copy (da_rom, da_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 (da_dib.select_code)))
|
|
return SCPE_IERR; /* return an internal error if the copy failed */
|
|
else
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
|
|
/* Amigo disc global SCP routines */
|
|
|
|
|
|
/* Load or unload a unit's heads.
|
|
|
|
The heads are automatically loaded when a unit is attached and unloaded when
|
|
a unit is detached. While a unit is attached, the heads may be manually
|
|
unloaded; this yields a "not ready" status if the unit is accessed. An
|
|
unloaded drive may be manually loaded, returning the unit to "ready" status.
|
|
|
|
The ICD controller sets Drive Attention status when the heads unload and also
|
|
asserts a parallel poll response if the heads unload while in idle state 2
|
|
(i.e., after an End command).
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The 13365 manual says on page 28 that Drive Attention status is
|
|
"Generated whenever...the drive unloads and the controller is in Idle
|
|
State 2 or 3." However, the ICD diagnostic tests for Drive Attention
|
|
status on head unload immediately after the Request Status command that
|
|
completes the previous step, which leaves the controller in idle state 1.
|
|
|
|
Moreover, the diagnostic does NOT check for Drive Attention status if the
|
|
Amigo ID is 2 (MAC controller). But the 12745 manual says on page 3-7
|
|
that the status is returned if "...Drive becomes not ready (heads
|
|
unload)" with no mention of controller state.
|
|
|
|
It appears as though the diagnostic test is exactly backward. However,
|
|
we match the diagnostic expectation below.
|
|
*/
|
|
|
|
t_stat da_load_unload (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
|
|
{
|
|
const int32 unit = uptr - da_unit; /* calculate the unit number */
|
|
const t_bool load = (value != UNIT_UNLOAD); /* true if the heads are loading */
|
|
t_stat result;
|
|
|
|
result = dl_load_unload (&icd_cntlr [unit], uptr, load); /* load or unload the heads */
|
|
|
|
if (result == SCPE_OK && ! load) { /* was the unload successful? */
|
|
icd_cntlr [unit].status = drive_attention; /* set Drive Attention status */
|
|
|
|
if (uptr->OP == End) /* is the controller in idle state 2? */
|
|
di_poll_response (da, unit, SET); /* enable PPR */
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
/* Amigo disc global bus routines */
|
|
|
|
|
|
/* Accept a data byte from the bus.
|
|
|
|
The indicated unit is offered a byte that has been sourced to the bus. The
|
|
routine returns TRUE or FALSE to indicate whether or not it accepted the
|
|
byte.
|
|
|
|
Commands from the bus may be universal (applying to all acceptors) or
|
|
addressed (applying only to those acceptors that have been addressed to
|
|
listen). Data bytes are accepted only if the unit has been addressed to
|
|
listen. As we are called for a data transfer or an addressed command only if
|
|
we are currently listening, the only bytes that we do not accept are primary
|
|
talk or listen commands directed to another address, or secondary commands
|
|
when we are not addressed to listen.
|
|
|
|
This routine handles the HP-IB protocol. The type of byte passed is
|
|
determined by the state of the ATN signal and, if ATN is asserted, by the
|
|
high-order bits of the value. Most of the work involves decoding secondary
|
|
commands and their associated data parameters. The interface state is
|
|
changed as needed to track the command protocol. The states processed in
|
|
this routine are:
|
|
|
|
opcode_wait
|
|
===========
|
|
|
|
A Receive Disc Command secondary has been received, and the interface is
|
|
waiting for the opcode that should follow.
|
|
|
|
parameter_wait
|
|
==============
|
|
|
|
A disc opcode or interface command has been received, and the interface is
|
|
waiting for a parameter byte that should follow.
|
|
|
|
write_wait
|
|
==========
|
|
|
|
A disc write command has been received, and the interface is waiting for
|
|
the Receive Write Data secondary that should follow.
|
|
|
|
read_wait
|
|
=========
|
|
|
|
A disc read command has been received, and the interface is waiting for the
|
|
Send Read Data secondary that should follow.
|
|
|
|
status_wait
|
|
===========
|
|
|
|
A disc status command has been received, and the interface is waiting for
|
|
the Send Disc Status secondary that should follow.
|
|
|
|
write_xfer
|
|
==========
|
|
|
|
A disc write is in progress, and the interface is waiting for a data byte
|
|
that should follow.
|
|
|
|
error_sink
|
|
==========
|
|
|
|
A disc write has terminated with an error, and the interface is waiting to
|
|
absorb all of the remaining data bytes of the transfer.
|
|
|
|
|
|
Disc commands and parameters are assembled in the sector buffer before being
|
|
passed to the disc library to start the command. Once the command is
|
|
started, the interface state is set either to execute the command or to wait
|
|
for the receipt of a data transfer secondary before executing, depending on
|
|
the command.
|
|
|
|
Two disc command protocol errors are detected. First, an Illegal Opcode is
|
|
identified during the check for the expected number of disc command
|
|
parameters. This allows us to sink an arbitrary number of parameter bytes.
|
|
Second, an I/O Program Error occurs if an unsupported secondary is received
|
|
or the HP-IB sequence is incorrect. The latter occurs if a command has the
|
|
wrong number of parameters or a secondary data transfer sequence is invalid.
|
|
|
|
Disc commands that require data transfers (e.g., Read, Write, Request Status)
|
|
involve a pair of secondaries. The first transmits the command, and the
|
|
second transmits or receives the data. If one occurs without the other, an
|
|
I/O Program Error occurs.
|
|
|
|
A secondary or command that generates an I/O Program Error is always ignored.
|
|
Error recovery is as follows:
|
|
|
|
- An unsupported talk secondary sends a single data byte tagged with EOI.
|
|
|
|
- An unsupported listen secondary accepts and discards any accompanying data
|
|
bytes until EOI is asserted or an Unlisten is received.
|
|
|
|
- A supported command with too few parameter bytes or for which the last
|
|
parameter byte is not tagged with EOI (before unlisten) does nothing.
|
|
|
|
- A supported command with too many parameter bytes accepts and discards
|
|
excess parameter bytes until EOI is asserted or an Unlisten is received.
|
|
|
|
- A read or status command that is not followed by a Send Read Data or a
|
|
Send Disc Status secondary does nothing. The unexpected secondary is
|
|
executed normally.
|
|
|
|
- A write command that is not followed by a Receive Write Data secondary
|
|
does nothing. The unexpected secondary is executed normally.
|
|
|
|
- A Send Read Data or a Send Disc Status secondary that is not preceded by a
|
|
read or status command sends a single data byte tagged with EOI.
|
|
|
|
- A Receive Write Data secondary that is not preceded by a write command
|
|
accepts and discards data bytes until EOI is asserted or an Unlisten is
|
|
received.
|
|
|
|
The Amigo command sequence does not provide a byte count for disc read and
|
|
write commands, so the controller continues to source or accept data bytes
|
|
until the device is unaddressed. Normally, this is done by an Unlisten or
|
|
Untalk. However, per IEEE-488, a listening device may be unaddressed by IFC,
|
|
by an Unlisten, or by addressing the device to talk, and a talking device may
|
|
be unaddressed by IFC, by addressing another device to talk (or no device via
|
|
Untalk), or by addressing the device to listen. Therefore, we must keep
|
|
track of whether the unit stopped talking or listening, and if it has, we
|
|
check for command termination.
|
|
|
|
If the controller is unaddressed in the middle of a sector transfer, the read
|
|
or write must be terminated cleanly to ensure that the disc image is
|
|
coherent. It is also permissible to untalk the controller before all of the
|
|
requested status bytes are returned.
|
|
|
|
In addition, the controller has no way to inform the host that an error has
|
|
occurred that prevents the command from continuing. For example, if a data
|
|
error is encountered while reading or a protected track is encountered while
|
|
writing, the controller must still source or sink data bytes until the
|
|
command is terminated by the host. The controller handles read errors by
|
|
sourcing a single data byte tagged with EOI and write errors by sinking data
|
|
bytes until EOI is seen or the unit is unaddressed.
|
|
|
|
Therefore, if the unit is unaddressed while a read, write, or status command
|
|
is transferring data, the unit service must be scheduled to end the current
|
|
command. Unaddressing while an error condition is present merely terminates
|
|
the source or sink operation.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The 13365 manual does not indicate that the controller responds to
|
|
Universal Clear, but the 12992H loader ROM issues this primary and
|
|
expects the controller to initialize.
|
|
|
|
2. It is not necessary to check for listening when processing addressed
|
|
commands, as only listeners are called by the bus source.
|
|
*/
|
|
|
|
t_bool da_bus_accept (uint32 unit, uint8 data)
|
|
{
|
|
const uint8 message_address = data & BUS_ADDRESS;
|
|
t_bool accepted = TRUE;
|
|
t_bool initiated = FALSE;
|
|
t_bool addressed = FALSE;
|
|
t_bool stopped_listening = FALSE;
|
|
t_bool stopped_talking = FALSE;
|
|
char action [40] = "";
|
|
uint32 my_address;
|
|
|
|
if (di [da].bus_cntl & BUS_ATN) { /* is it a bus command (ATN asserted)? */
|
|
switch (data & BUS_GROUP) { /* dispatch the bus group */
|
|
|
|
case BUS_PCG: /* primary command group */
|
|
switch (message_address) {
|
|
|
|
case 0x04: /* selected device clear */
|
|
case 0x05: /* SDC with parity freeze */
|
|
case 0x14: /* universal clear */
|
|
if (DEBUG_PRI (da_dev, DEB_RWSC))
|
|
fprintf (sim_deb, ">>DA rwsc: Unit %d device cleared\n", unit);
|
|
|
|
sim_cancel (&da_unit [unit]); /* cancel any in-progress command */
|
|
dl_idle_controller (&icd_cntlr [unit]); /* idle the controller */
|
|
if_dsj [unit] = 0; /* clear DSJ */
|
|
if_state [unit] = idle; /* idle the interface */
|
|
di_poll_response (da, unit, SET); /* enable PPR */
|
|
|
|
if (DEBUG_PRI (da_dev, DEB_XFER))
|
|
strcpy (action, "device clear");
|
|
break;
|
|
|
|
|
|
default: /* unsupported universal command */
|
|
break; /* universals are always accepted */
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
case BUS_LAG: /* listen address group */
|
|
my_address = GET_BUSADR (da_unit [unit].flags); /* get my bus address */
|
|
|
|
if (message_address == my_address) { /* is it my listen address? */
|
|
di [da].listeners |= (1 << unit); /* set my listener bit */
|
|
di [da].talker &= ~(1 << unit); /* clear my talker bit */
|
|
|
|
addressed = TRUE; /* unit is now addressed */
|
|
stopped_talking = TRUE; /* MLA stops the unit from talking */
|
|
|
|
if (DEBUG_PRI (da_dev, DEB_XFER))
|
|
sprintf (action, "listen %d", message_address);
|
|
}
|
|
|
|
else if (message_address == BUS_UNADDRESS) { /* is it an Unlisten? */
|
|
di [da].listeners = 0; /* clear all of the listeners */
|
|
|
|
stopped_listening = TRUE; /* UNL stops the unit from listening */
|
|
|
|
if (DEBUG_PRI (da_dev, DEB_XFER))
|
|
strcpy (action, "unlisten");
|
|
}
|
|
|
|
else /* other listen addresses */
|
|
accepted = FALSE; /* are not accepted */
|
|
|
|
break;
|
|
|
|
|
|
case BUS_TAG: /* talk address group */
|
|
my_address = GET_BUSADR (da_unit [unit].flags); /* get my bus address */
|
|
|
|
if (message_address == my_address) { /* is it my talk address? */
|
|
di [da].talker = (1 << unit); /* set my talker bit and clear the others */
|
|
di [da].listeners &= ~(1 << unit); /* clear my listener bit */
|
|
|
|
addressed = TRUE; /* the unit is now addressed */
|
|
stopped_listening = TRUE; /* MTA stops the unit from listening */
|
|
|
|
if (DEBUG_PRI (da_dev, DEB_XFER))
|
|
sprintf (action, "talk %d", message_address);
|
|
}
|
|
|
|
else { /* it is some other talker (or Untalk) */
|
|
di [da].talker &= ~(1 << unit); /* clear my talker bit */
|
|
|
|
stopped_talking = TRUE; /* UNT or OTA stops the unit from talking */
|
|
|
|
if (message_address != BUS_UNADDRESS) /* other talk addresses */
|
|
accepted = FALSE; /* are not accepted */
|
|
|
|
else /* it's an Untalk */
|
|
if (DEBUG_PRI (da_dev, DEB_XFER))
|
|
strcpy (action, "untalk");
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
case BUS_SCG: /* secondary command group */
|
|
icd_cntlr [unit].index = 0; /* reset the buffer index */
|
|
|
|
if (di [da].listeners & (1 << unit)) { /* is it a listen secondary? */
|
|
if (if_state [unit] == write_wait /* if we're waiting for a write data secondary */
|
|
&& message_address != 0x00) /* but it's not there, */
|
|
abort_command (unit, io_program_error, /* then abort the pending command */
|
|
idle); /* and process the new command */
|
|
|
|
switch (message_address) { /* dispatch the listen secondary */
|
|
|
|
case 0x00: /* Receive Write Data */
|
|
if (if_state [unit] != write_wait) /* if we're not expecting it */
|
|
abort_command (unit, io_program_error, /* abort and sink any data */
|
|
error_sink);
|
|
else { /* the sequence is correct */
|
|
if_state [unit] = command_exec; /* the command is ready to execute */
|
|
da_unit [unit].wait = icd_cntlr [unit].cmd_time; /* schedule the unit */
|
|
di_bus_control (da, unit, BUS_NRFD, 0); /* assert NRFD to hold off the card */
|
|
}
|
|
|
|
initiated = TRUE; /* log the command or abort initiation */
|
|
break;
|
|
|
|
case 0x08: /* disc commands */
|
|
if_command [unit] = disc_command; /* set the command and wait */
|
|
if_state [unit] = opcode_wait; /* for the opcode that must follow */
|
|
break;
|
|
|
|
case 0x09: /* CRC (Listen) */
|
|
if_command [unit] = crc_listen; /* set up the command */
|
|
if_state [unit] = error_sink; /* sink any data that will be coming */
|
|
initiated = TRUE; /* log the command initiation */
|
|
break;
|
|
|
|
case 0x10: /* Amigo Clear */
|
|
if_command [unit] = amigo_clear; /* set up the command */
|
|
if_state [unit] = parameter_wait; /* a parameter must follow */
|
|
icd_cntlr [unit].length = 1; /* set to expect one (unused) byte */
|
|
break;
|
|
|
|
case 0x1E: /* Write Loopback */
|
|
if_command [unit] = write_loopback; /* set up the command */
|
|
if_state [unit] = write_xfer; /* data will be coming */
|
|
icd_cntlr [unit].length = 16; /* accept only the first 16 bytes */
|
|
initiated = TRUE; /* log the command initiation */
|
|
break;
|
|
|
|
case 0x1F: /* Initiate Self-Test */
|
|
if_command [unit] = initiate_self_test; /* set up the command */
|
|
if_state [unit] = parameter_wait; /* a parameter must follow */
|
|
icd_cntlr [unit].length = 1; /* set to expect the test ID byte */
|
|
break;
|
|
|
|
default: /* an unsupported listen secondary was received */
|
|
abort_command (unit, io_program_error, /* abort and sink any data */
|
|
error_sink); /* that might accompany the command */
|
|
initiated = TRUE; /* log the abort initiation */
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
else if (di [da].talker & (1 << unit)) { /* is it a talk secondary? */
|
|
da_unit [unit].wait = icd_cntlr [unit].cmd_time; /* these are always scheduled and */
|
|
initiated = TRUE; /* logged as initiated */
|
|
|
|
if (if_state [unit] == read_wait /* if we're waiting for a send data secondary */
|
|
&& message_address != 0x00 /* but it's not there */
|
|
|| if_state [unit] == status_wait /* or a send status secondary, */
|
|
&& message_address != 0x08) /* but it's not there */
|
|
abort_command (unit, io_program_error, /* then abort the pending command */
|
|
idle); /* and process the new command */
|
|
|
|
switch (message_address) { /* dispatch the talk secondary */
|
|
|
|
case 0x00: /* Send Read Data */
|
|
if (if_state [unit] != read_wait) /* if we're not expecting it */
|
|
abort_command (unit, io_program_error, /* abort and source a data byte */
|
|
error_source); /* tagged with EOI */
|
|
else
|
|
if_state [unit] = command_exec; /* the command is ready to execute */
|
|
break;
|
|
|
|
case 0x08: /* Read Status */
|
|
if (if_state [unit] != status_wait) /* if we're not expecting it, */
|
|
abort_command (unit, io_program_error, /* abort and source a data byte */
|
|
error_source); /* tagged with EOI */
|
|
else /* all status commands */
|
|
if_state [unit] = read_xfer; /* are ready to transfer data */
|
|
break;
|
|
|
|
case 0x09: /* CRC (Talk) */
|
|
if_command [unit] = crc_talk; /* set up the command */
|
|
if_state [unit] = read_xfer; /* data will be going */
|
|
break;
|
|
|
|
case 0x10: /* Device-Specified Jump */
|
|
if_command [unit] = device_specified_jump; /* set up the command */
|
|
if_state [unit] = read_xfer; /* data will be going */
|
|
break;
|
|
|
|
case 0x1E: /* Read Loopback */
|
|
if_command [unit] = read_loopback; /* set up the command */
|
|
if_state [unit] = read_xfer; /* data will be going */
|
|
break;
|
|
|
|
case 0x1F: /* Return Self-Test Result */
|
|
if_command [unit] = return_self_test_result; /* set up the command */
|
|
if_state [unit] = read_xfer; /* data will be going */
|
|
icd_cntlr [unit].length = 1; /* return one byte that indicates */
|
|
buffer [0] = 0; /* that the self-test passed */
|
|
break;
|
|
|
|
default: /* an unsupported talk secondary was received */
|
|
abort_command (unit, io_program_error, /* abort and source a data byte */
|
|
error_source); /* tagged with EOI */
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
else { /* the unit is not addressed */
|
|
my_address = GET_BUSADR (da_unit [unit].flags); /* get my bus address */
|
|
|
|
if (di [da].talker == 0 && di [da].listeners == 0 /* if there are no talkers or listeners */
|
|
&& message_address == my_address) { /* and this is my secondary address, */
|
|
if_command [unit] = amigo_identify; /* then this is an Amigo ID sequence */
|
|
if_state [unit] = command_exec; /* set up for execution */
|
|
da_unit [unit].wait = icd_cntlr [unit].cmd_time; /* schedule the unit */
|
|
initiated = TRUE; /* log the command initiation */
|
|
}
|
|
|
|
else /* unaddressed secondaries */
|
|
accepted = FALSE; /* are not accepted */
|
|
}
|
|
|
|
|
|
if (accepted) { /* was the command accepted? */
|
|
if (DEBUG_PRI (da_dev, DEB_XFER))
|
|
sprintf (action, "secondary %02XH", message_address);
|
|
|
|
if (if_command [unit] != amigo_identify) /* disable PPR for all commands */
|
|
di_poll_response (da, unit, CLEAR); /* except Amigo ID */
|
|
}
|
|
|
|
break; /* end of secondary processing */
|
|
}
|
|
|
|
|
|
if (addressed && sim_is_active (&da_unit [unit])) { /* is the unit being addressed while it is busy? */
|
|
if_state [unit] = command_wait; /* change the interface state to wait */
|
|
di_bus_control (da, unit, BUS_NRFD, 0); /* and assert NRFD to hold off the card */
|
|
|
|
if (DEBUG_PRI (da_dev, DEB_RWSC))
|
|
fprintf (sim_deb, ">>DA rwsc: Unit %d addressed while controller is busy\n", unit);
|
|
}
|
|
|
|
if (stopped_listening) { /* was the unit Unlistened? */
|
|
if (icd_cntlr [unit].state == cntlr_busy) /* if the controller is busy, */
|
|
complete_write (unit); /* then check for write completion */
|
|
|
|
else if (if_command [unit] == invalid) /* if a command was aborting, */
|
|
complete_abort (unit); /* then complete it */
|
|
|
|
else if (if_state [unit] == opcode_wait /* if waiting for an opcode */
|
|
|| if_state [unit] == parameter_wait) /* or a parameter, */
|
|
abort_command (unit, io_program_error, idle); /* then abort the pending command */
|
|
}
|
|
|
|
else if (stopped_talking) { /* was the unit Untalked? */
|
|
if (icd_cntlr [unit].state == cntlr_busy) /* if the controller is busy, */
|
|
complete_read (unit); /* then check for read completion */
|
|
|
|
else if (if_command [unit] == invalid) /* if a command was aborting, */
|
|
complete_abort (unit); /* then complete it */
|
|
}
|
|
} /* end of bus command processing */
|
|
|
|
|
|
else { /* it is bus data (ATN is denied) */
|
|
switch (if_state [unit]) { /* dispatch the interface state */
|
|
|
|
case opcode_wait: /* waiting for an opcode */
|
|
if (DEBUG_PRI (da_dev, DEB_XFER))
|
|
sprintf (action, "opcode %02XH", data & DL_OPCODE_MASK);
|
|
|
|
buffer [0] = SET_UPPER (data); /* set the opcode into the buffer */
|
|
|
|
if (dl_prepare_command (&icd_cntlr [unit], /* is the command valid? */
|
|
da_unit, unit)) {
|
|
if_state [unit] = parameter_wait; /* set up to get the pad byte */
|
|
icd_cntlr [unit].index = 0; /* reset the word index for the next byte */
|
|
icd_cntlr [unit].length = /* convert the parameter count to bytes */
|
|
icd_cntlr [unit].length * 2 + 1; /* and include the pad byte */
|
|
}
|
|
|
|
else { /* the disc command is invalid */
|
|
abort_command (unit, illegal_opcode, /* abort the command */
|
|
error_sink); /* and sink any parameter bytes */
|
|
initiated = TRUE; /* log the abort initiation */
|
|
} /* (the unit cannot be busy) */
|
|
break;
|
|
|
|
|
|
case parameter_wait: /* waiting for a parameter */
|
|
if (DEBUG_PRI (da_dev, DEB_XFER))
|
|
sprintf (action, "parameter %02XH", data);
|
|
|
|
put_buffer_byte (&icd_cntlr [unit], data); /* add the byte to the buffer */
|
|
|
|
if (icd_cntlr [unit].length == 0) /* is this the last parameter? */
|
|
if (di [da].bus_cntl & BUS_EOI) /* does the host agree? */
|
|
initiated = start_command (unit); /* start the command and log the initiation */
|
|
|
|
else { /* the parameter count is wrong */
|
|
abort_command (unit, io_program_error, /* abort the command and sink */
|
|
error_sink); /* any additional parameter bytes */
|
|
initiated = TRUE; /* log the abort initiation */
|
|
}
|
|
break;
|
|
|
|
|
|
case write_xfer: /* transferring write data */
|
|
if (icd_cntlr [unit].length > 0) /* if there is more to transfer */
|
|
put_buffer_byte (&icd_cntlr [unit], data); /* then add the byte to the buffer */
|
|
|
|
/* fall into error_sink handler */
|
|
|
|
case error_sink: /* sinking data after an error */
|
|
if (DEBUG_PRI (da_dev, DEB_XFER))
|
|
sprintf (action, "data %03o", data);
|
|
|
|
if (di [da].bus_cntl & BUS_EOI) /* is this the last byte from the bus? */
|
|
icd_cntlr [unit].eod = SET; /* indicate EOD to the controller */
|
|
|
|
di_bus_control (da, unit, BUS_NRFD, 0); /* assert NRFD to hold off the card */
|
|
|
|
da_unit [unit].wait = icd_cntlr [unit].data_time; /* schedule the unit */
|
|
break;
|
|
|
|
|
|
default: /* data was received in the wrong state */
|
|
abort_command (unit, io_program_error, /* report the error */
|
|
error_sink); /* and sink any data that follows */
|
|
|
|
if (DEBUG_PRI (da_dev, DEB_XFER))
|
|
sprintf (action, "unhandled data %03o", data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if (accepted && DEBUG_PRI (da_dev, DEB_XFER))
|
|
fprintf (sim_deb, ">>DA xfer: HP-IB address %d accepted %s\n",
|
|
GET_BUSADR (da_unit [unit].flags), action);
|
|
|
|
if (da_unit [unit].wait > 0) /* was service requested? */
|
|
activate_unit (&da_unit [unit]); /* schedule the unit */
|
|
|
|
if (initiated && DEBUG_PRI (da_dev, DEB_RWSC))
|
|
if (if_command [unit] == disc_command)
|
|
fprintf (sim_deb, ">>DA rwsc: Unit %d position %" T_ADDR_FMT "d %s disc command initiated\n",
|
|
unit, da_unit [unit].pos, dl_opcode_name (ICD, icd_cntlr [unit].opcode));
|
|
else
|
|
fprintf (sim_deb, ">>DA rwsc: Unit %d %s command initiated\n",
|
|
unit, if_command_name [if_command [unit]]);
|
|
|
|
return accepted; /* indicate the acceptance condition */
|
|
}
|
|
|
|
|
|
/* Respond to the bus control lines.
|
|
|
|
The indicated unit is notified of the new control state on the bus. There
|
|
are two conditions to which we must respond:
|
|
|
|
1. An Interface Clear is initiated. IFC unaddresses all units, so any
|
|
in-progress disc command must be terminated as if an Untalk and Unlisten
|
|
were accepted from the data bus.
|
|
|
|
2. Attention and Not Ready for Data are denied. A device addressed to talk
|
|
must wait for ATN to deny before data may be sent. Also, a listener that
|
|
has asserted NRFD must deny it before a talker may send data. If the
|
|
interface is sending data and both ATN and NRFD are denied, then we
|
|
reschedule the service routine to send the next byte.
|
|
*/
|
|
|
|
void da_bus_respond (CARD_ID card, uint32 unit, uint8 new_cntl)
|
|
{
|
|
if (new_cntl & BUS_IFC) { /* is interface clear asserted? */
|
|
di [da].listeners = 0; /* perform an Unlisten */
|
|
di [da].talker = 0; /* and an Untalk */
|
|
|
|
if (icd_cntlr [unit].state == cntlr_busy) { /* is the controller busy? */
|
|
complete_write (unit); /* check for write completion */
|
|
complete_read (unit); /* or read completion */
|
|
|
|
if (da_unit [unit].wait > 0) /* is service needed? */
|
|
activate_unit (&da_unit [unit]); /* activate the unit */
|
|
}
|
|
|
|
else if (if_command [unit] == invalid) /* if a command was aborting, */
|
|
complete_abort (unit); /* then complete it */
|
|
|
|
else if (if_state [unit] == opcode_wait /* if we're waiting for an opcode */
|
|
|| if_state [unit] == parameter_wait) /* or a parameter, */
|
|
abort_command (unit, io_program_error, idle); /* then abort the pending command */
|
|
}
|
|
|
|
if (!(new_cntl & (BUS_ATN | BUS_NRFD)) /* is the card in data mode and ready for data? */
|
|
&& (if_state [unit] == read_xfer /* is the interface waiting to send data */
|
|
|| if_state [unit] == error_source)) /* or source error bytes? */
|
|
da_service (&da_unit [unit]); /* start or resume the transfer */
|
|
}
|
|
|
|
|
|
|
|
/* Amigo disc local utility routines */
|
|
|
|
|
|
/* Start a command with parameters.
|
|
|
|
A command that has been waiting for all of its parameters to be received is
|
|
now ready to start. If this is a disc command, call the disc library to
|
|
validate the parameters and, if they are OK, to start the command. Status
|
|
commands return the status values in the sector buffer and the number of
|
|
words that were returned in the buffer length, which we convert to a byte
|
|
count.
|
|
|
|
If the disc command was accepted, the library returns a pointer to the unit
|
|
to be scheduled. For an ICD controller, the unit is always the one currently
|
|
addressed, so we simply test if the return is not NULL. If it isn't, then we
|
|
set the next interface state as determined by the command that is executing.
|
|
For example, a Read command sets the interface to read_wait status in order
|
|
to wait until the accompanying Send Read Data secondary is received.
|
|
|
|
If the return is NULL, then the command was rejected, so we set DSJ = 1 and
|
|
leave the interface state in parameter_wait; the controller status will have
|
|
been set to the reason for the rejection.
|
|
|
|
If the next interface state is command_exec, then the disc command is ready
|
|
for execution, and we return TRUE to schedule the unit service. Otherwise,
|
|
we return FALSE, and the appropriate action will be taken by the caller.
|
|
|
|
For all other commands, execution begins as soon as the correct parameters
|
|
are received, so we set command_exec state and return TRUE. (Only Amigo
|
|
Clear and Initiate Self Test require parameters, so they will be the only
|
|
other commands that must be started here.)
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. As the ICD implementation does not need to differentiate between unit and
|
|
controller commands, the return value from the dl_start_command routine
|
|
is not used other than as an indication of success or failure.
|
|
*/
|
|
|
|
static t_bool start_command (uint32 unit)
|
|
{
|
|
if (if_command [unit] == disc_command) { /* are we starting a disc command? */
|
|
if (dl_start_command (&icd_cntlr [unit], da_unit, unit)) { /* start the command; was it successful? */
|
|
icd_cntlr [unit].length = icd_cntlr [unit].length * 2; /* convert the return length from words to bytes */
|
|
if_state [unit] = next_state [icd_cntlr [unit].opcode]; /* set the next interface state */
|
|
}
|
|
|
|
else /* the command was rejected */
|
|
if_dsj [unit] = 1; /* so indicate an error */
|
|
|
|
if (if_state [unit] == command_exec) /* if the command is executing */
|
|
return TRUE; /* activate the unit */
|
|
|
|
else { /* if we must wait */
|
|
da_unit [unit].wait = 0; /* for another secondary, */
|
|
return FALSE; /* then skip the activation */
|
|
}
|
|
}
|
|
|
|
else { /* all other commands */
|
|
if_state [unit] = command_exec; /* execute as soon */
|
|
da_unit [unit].wait = icd_cntlr [unit].cmd_time; /* as they */
|
|
return TRUE; /* are received */
|
|
}
|
|
}
|
|
|
|
|
|
/* Abort an in-process command.
|
|
|
|
A command sequence partially received via the bus must be aborted. The cause
|
|
might be an unknown secondary, an illegal disc command opcode, an improper
|
|
secondary sequence (e.g., a Read not followed by Send Read Data), an
|
|
incorrect number of parameters, or unaddressing before the sequence was
|
|
complete. In any event, the controller and interface are set to an abort
|
|
state, and the DSJ value is set to 1 to indicate an error.
|
|
*/
|
|
|
|
static void abort_command (uint32 unit, CNTLR_STATUS status, IF_STATE state)
|
|
{
|
|
if_command [unit] = invalid; /* indicate an invalid command */
|
|
if_state [unit] = state; /* set the interface state as directed */
|
|
if_dsj [unit] = 1; /* set DSJ to indicate an error condition */
|
|
dl_end_command (&icd_cntlr [unit], status); /* place the disc controller into the wait state */
|
|
return;
|
|
}
|
|
|
|
|
|
/* Complete an in-process read command.
|
|
|
|
An Untalk terminates a Read, Read Full Sector, Read Without Verify, Read With
|
|
Offset, or Cold Load Read command, which must be tied off cleanly by setting
|
|
the end-of-data condition and calling the service routine. This is required
|
|
only if the read has not already aborted (e.g., for an auto-seek error).
|
|
|
|
If a read is in progress, the controller will be busy, and the interface
|
|
state will be either command_exec (if between sectors) or read_xfer (if
|
|
within a sector). We set up the end phase for the command and schedule the
|
|
disc service to tidy up.
|
|
|
|
If a read has aborted, the controller will be waiting, and the interface
|
|
state will be error_source. In this latter case, we no nothing, as the
|
|
controller has already set the required error status.
|
|
|
|
We must be careful NOT to trigger on an Untalk that may follow the opcode and
|
|
precede the Send Read Data sequence. In this case, the controller will be
|
|
busy, but the interface state will be either read_wait or status_wait.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The test for controller busy is made before calling this routine. This
|
|
saves the call overhead for the most common case, which is the card is
|
|
being unaddressed after command completion.
|
|
|
|
2. There is no need to test if we are processing a disc command, as the
|
|
controller would not be busy otherwise.
|
|
|
|
3. If an auto-seek will be needed to continue the read, but the seek will
|
|
fail, then an extra delay is inserted before the service call to start
|
|
the next sector. Once an Untalk is received, this delay is no longer
|
|
needed, so it is cancelled before rescheduling the service routine.
|
|
*/
|
|
|
|
static void complete_read (uint32 unit)
|
|
{
|
|
if ((if_state [unit] == command_exec /* is a command executing */
|
|
|| if_state [unit] == read_xfer) /* or is data transferring */
|
|
&& (dl_classify (icd_cntlr [unit]) == class_read /* and the controller is executing */
|
|
|| dl_classify (icd_cntlr [unit]) == class_status)) { /* a read or status command? */
|
|
icd_cntlr [unit].eod = SET; /* set the end of data flag */
|
|
|
|
if_state [unit] = command_exec; /* set to execute */
|
|
da_unit [unit].PHASE = end_phase; /* the completion phase */
|
|
|
|
sim_cancel (&da_unit [unit]); /* cancel the EOT delay */
|
|
da_unit [unit].wait = icd_cntlr [unit].data_time; /* reschedule for completion */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Complete an in-process write command.
|
|
|
|
Normally, the host sends a byte tagged with EOI to end a Write, Write Full
|
|
Sector, or Initialize command. However, an Unlisten may terminate a write,
|
|
which must be tied off cleanly by setting the end-of-data condition and
|
|
calling the service routine. This is required only if the write has not
|
|
already aborted (e.g., for a write-protected disc).
|
|
|
|
If a write is in progress, the controller will be busy, and the interface
|
|
state will be either command_exec (if between sectors) or write_xfer (if
|
|
within a sector). We set up the end phase for the command and schedule the
|
|
disc service to tidy up.
|
|
|
|
If a write has aborted, the controller will be waiting, and the interface
|
|
state will be error_sink. In this latter case, we do nothing, as the
|
|
controller has already set the required error status.
|
|
|
|
We must be careful NOT to trigger on the Unlisten that may follow the opcode
|
|
and precede the Receive Write Data sequence. In this case, the controller
|
|
will be busy, but the interface state will be write_wait.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The test for controller busy is made before calling this routine. This
|
|
saves the call overhead for the most common case, which is the card is
|
|
being unaddressed after command completion.
|
|
|
|
2. There is no need to test if we are processing a disc command, as the
|
|
controller would not be busy otherwise.
|
|
*/
|
|
|
|
static void complete_write (uint32 unit)
|
|
{
|
|
if ((if_state [unit] == command_exec /* is a command executing */
|
|
|| if_state [unit] == write_xfer) /* or is data transferring */
|
|
&& dl_classify (icd_cntlr [unit]) == class_write) { /* and the controller is executing a write? */
|
|
icd_cntlr [unit].eod = SET; /* set the end of data flag */
|
|
|
|
if_state [unit] = command_exec; /* set to execute */
|
|
da_unit [unit].PHASE = end_phase; /* the completion phase */
|
|
da_unit [unit].wait = icd_cntlr [unit].data_time; /* ensure that the controller will finish */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Complete an in-process command abort.
|
|
|
|
Errors in the command protocol begin an abort sequence that may involve
|
|
sourcing or sinking bytes to allow the sequence to complete as expected by
|
|
the CPU. Unaddressing the unit terminates the aborted command.
|
|
|
|
If an abort is in progress, and the interface is not idle, the end-of-data
|
|
indication is set, and the disc service routine is called directly to process
|
|
the completion of the abort. The service routine will terminate the
|
|
error_source or error_sink state cleanly and then idle the interface.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The test for an abort-in-progress is made before calling this routine.
|
|
This saves the call overhead for the most common case, which is the card
|
|
is being unaddressed after normal command completion.
|
|
*/
|
|
|
|
static void complete_abort (uint32 unit)
|
|
{
|
|
if (if_state [unit] != idle) { /* is the interface busy? */
|
|
icd_cntlr [unit].eod = SET; /* set the end of data flag */
|
|
da_service (&da_unit [unit]); /* and process the abort completion */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Get a byte from the sector buffer.
|
|
|
|
The next available byte in the sector buffer is returned to the caller. The
|
|
determination of which byte of the 16-bit buffer word to return is made by
|
|
the polarity of the buffer byte count. The count always begins with an even
|
|
number, as it is set by doubling the word count returned from the disc
|
|
library. Therefore, because we decrement the count first, the upper byte is
|
|
indicated by an odd count, and the lower byte is indicated by an even count.
|
|
The buffer index is incremented only after the lower byte is returned.
|
|
*/
|
|
|
|
static uint8 get_buffer_byte (CVPTR cvptr)
|
|
{
|
|
cvptr->length = cvptr->length - 1; /* count the byte */
|
|
|
|
if (cvptr->length & 1) /* is the upper byte next? */
|
|
return GET_UPPER (buffer [cvptr->index]); /* return the byte */
|
|
else /* the lower byte is next */
|
|
return GET_LOWER (buffer [cvptr->index++]); /* return the byte and bump the word index */
|
|
}
|
|
|
|
|
|
/* Put a byte into the sector buffer.
|
|
|
|
The supplied byte is stored in the sector buffer. The determination of which
|
|
byte of the 16-bit buffer word to store is made by the polarity of the buffer
|
|
byte count. The count always begins with an even number, as it is set by
|
|
doubling the word count returned from the disc library. Therefore, because
|
|
we decrement the count first, the upper byte is indicated by an odd count,
|
|
and the lower byte is indicated by an even count. The buffer index is
|
|
incremented only after the lower byte is stored.
|
|
*/
|
|
|
|
static void put_buffer_byte (CVPTR cvptr, uint8 data)
|
|
{
|
|
cvptr->length = cvptr->length - 1; /* count the byte */
|
|
|
|
if (cvptr->length & 1) /* is the upper byte next? */
|
|
buffer [cvptr->index] = SET_UPPER (data); /* save the byte */
|
|
else /* the lower byte is next */
|
|
buffer [cvptr->index++] |= SET_LOWER (data); /* merge the byte and bump the word index */
|
|
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)
|
|
{
|
|
int32 unit;
|
|
t_stat result;
|
|
|
|
if (DEBUG_PRI (da_dev, DEB_SERV)) {
|
|
unit = uptr - da_unit;
|
|
|
|
fprintf (sim_deb, ">>DA serv: Unit %d state %s delay %d service scheduled\n",
|
|
unit, if_state_name [if_state [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 */
|
|
}
|