| /* 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 | |
| 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, char *cptr); | |
| t_stat da_detach (UNIT *uptr); | |
| /* Amigo disc global SCP routines */ | |
| t_stat da_load_unload (UNIT *uptr, int32 value, 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, 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, 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 */ | |
| } |