/* hp2100_di_da.c: HP 12821A HP-IB Disc Interface simulator for Amigo disc drives | |
Copyright (c) 2011-2017, J. David Bryan | |
Permission is hereby granted, free of charge, to any person obtaining a | |
copy of this software and associated documentation files (the "Software"), | |
to deal in the Software without restriction, including without limitation | |
the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
and/or sell copies of the Software, and to permit persons to whom the | |
Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
THE 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 | |
11-Jul-17 JDB Renamed "ibl_copy" to "cpu_ibl" | |
15-Mar-17 JDB Changed DEBUG_PRI calls to tprintfs | |
09-Mar-17 JDB Deprecated LOCKED/WRITEENABLED for PROTECT/UNPROTECT | |
27-Feb-17 JDB ibl_copy no longer returns a status code | |
17-Jan-17 JDB Changed to use new byte accessors in hp2100_defs.h | |
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, February 1980) | |
- HP 7910 Disc Drive Service Manual | |
(07910-90903, April 1981) | |
- 12745D Disc Controller (13037) to HP-IB Adapter Kit Installation and Service Manual | |
(12745-90911, September 1983) | |
- HP's 5 1/4-Inch Winchester Disc Drive Service Documentation | |
(09134-90032, August 1983) | |
- HP 12992 Loader ROMs Installation Manual | |
(12992-90001, April 1986) | |
- RTE Driver DVA32 Source | |
(92084-18708, revision 2540) | |
- IEEE Standard Digital Interface for Programmable Instrumentation | |
(IEEE-488A-1980, September 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_cpu.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, da_dib), | |
/* Mask Value Match Value Print String Match String Validation Display Descriptor */ | |
/* ------------ ------------ ----------------- --------------- ---------------- ------- ---------- */ | |
{ 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, "protected", "PROTECT", NULL, NULL, NULL }, | |
{ UNIT_WLK, 0, "unprotected", "UNPROTECT", NULL, NULL, NULL }, | |
{ UNIT_WLK, UNIT_WLK, NULL, "LOCKED", NULL, NULL, NULL }, | |
{ UNIT_WLK, 0, NULL, "WRITEENABLED", NULL, NULL, NULL }, | |
{ UNIT_FMT, UNIT_FMT, "format enabled", "FORMAT", NULL, NULL, NULL }, | |
{ UNIT_FMT, 0, "format disabled", "NOFORMAT", NULL, NULL, NULL }, | |
{ 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 */ | |
tprintf (da_dev, DEB_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) /* did an internal error occur? */ | |
if (if_state [unit] == command_exec | |
&& if_command [unit] == disc_command) | |
tprintf (da_dev, DEB_RWSC, "Unit %d %s command %s phase service not handled\n", | |
unit, | |
dl_opcode_name (ICD, (CNTLR_OPCODE) uptr->OP), | |
dl_phase_name ((CNTLR_PHASE) uptr->PHASE)); | |
else | |
tprintf (da_dev, DEB_RWSC, "Unit %d %s state %s service not handled\n", | |
unit, | |
if_command_name [if_command [unit]], | |
if_state_name [if_state [unit]]); | |
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 */ | |
tprintf (da_dev, DEB_RWSC, "Unit %d %s disc command completed\n", | |
unit, dl_opcode_name (ICD, cvptr->opcode)); | |
} | |
else /* an interface command completed */ | |
tprintf (da_dev, DEB_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 */ | |
cpu_ibl (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_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 */ | |
tprintf (da_dev, DEB_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 (TRACING (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 (TRACING (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 (TRACING (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 (TRACING (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 (TRACING (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 (TRACING (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 */ | |
tprintf (da_dev, DEB_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 (TRACING (da_dev, DEB_XFER)) | |
sprintf (action, "opcode %02XH", data & DL_OPCODE_MASK); | |
buffer [0] = TO_WORD (data, 0); /* 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 (TRACING (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 (TRACING (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 (TRACING (da_dev, DEB_XFER)) | |
sprintf (action, "unhandled data %03o", data); | |
break; | |
} | |
} | |
if (accepted) | |
tprintf (da_dev, DEB_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) | |
if (if_command [unit] == disc_command) | |
tprintf (da_dev, DEB_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 | |
tprintf (da_dev, DEB_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 UPPER_BYTE (buffer [cvptr->index]); /* return the byte */ | |
else /* the lower byte is next */ | |
return LOWER_BYTE (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] = TO_WORD (data, 0); /* save the byte */ | |
else /* the lower byte is next */ | |
buffer [cvptr->index++] |= TO_WORD (0, 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) | |
{ | |
t_stat result; | |
tprintf (da_dev, DEB_SERV, "Unit %d state %s delay %d service scheduled\n", | |
uptr - da_unit, if_state_name [if_state [uptr - da_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 */ | |
} |