/* hp2100_di_da.c: HP 12821A HP-IB Disc Interface simulator for Amigo disc drives Copyright (c) 2011-2021, 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 02-Apr-21 JDB Changed u3-u6 synonyms to title case to avoid conflicts 07-Feb-21 JDB Changed CNVARS register macro from BRDATA to SVDATA 02-Dec-20 JDB RESET no longer presets the interface 11-Jul-18 JDB Revised I/O model, changed global scope of variables to local 21-Feb-18 JDB ATTACH -N now creates a full-size disc image 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 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_io.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 * const 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 * const 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 local VM routines */ static t_stat da_reset (DEVICE *dptr); static t_stat da_boot (int32 unitno, DEVICE *dptr); static t_stat da_attach (UNIT *uptr, char *cptr); static t_stat da_detach (UNIT *uptr); /* Amigo disc local SCP routines */ static t_stat da_service (UNIT *uptr); static 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. Ideally, each corresponding field of the four controller state variable structures could appear as an arrayed register. Unfortunately, access to an array 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. While the UNIT_REG flag overrides this for UNIT arrays and increases the access span to the size of the UNIT structure, there is no generalization of this to other structures (e.g., there is no way to specify an array of structure fields where a given 32-bit field appears at, say, 92-byte offsets). As there is no way to display the fields, we include the controller array simply for SAVE/RESTORE. */ DEVICE da_dev; static DIB da_dib = { &di_interface, /* the device's I/O interface function pointer */ NULL, /* the power state function pointer */ DI_DA, /* the device's select code (02-77) */ da, /* the card index */ "12821A Disc Interface", /* the card description */ "12992H 7906H/7920H/7925H/9895 Disc Loader" /* the ROM description */ }; #define UNIT_FLAGS (UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | UNIT_DISABLE | UNIT_UNLOAD) static 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 */ }; static 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 }, { SVDATA (CNVARS, icd_cntlr) }, { NULL } }; static 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_DISABLE | DEV_DEBUG, /* 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). */ static 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 through 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 (not (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 (not (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. */ static 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. If a new file is specified, the file is initialized to its capacity by writing a zero to the last byte in the file. Implementation notes: 1. To conform exactly to the MAC responses would have required intercepting the SET 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. 2. The C standard says, "A binary stream need not meaningfully support fseek calls with a whence value of SEEK_END," so instead we determine the offset from the start of the file to the last byte and seek there. */ static t_stat da_attach (UNIT *uptr, char *cptr) { t_stat result; t_addr offset; const uint8 zero = 0; 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) { /* if the attach was successful */ di [da].acceptors |= (1 << unit); /* then set the unit's accepting bit */ if (sim_switches & SWMASK ('N')) { /* if this is a new disc image */ offset = (t_addr) /* then determine the offset of */ (uptr->capac * sizeof (int16) - sizeof zero); /* the last byte in a full-sized file */ if (sim_fseek (uptr->fileref, offset, SEEK_SET) != 0 /* seek to the last byte */ || fwrite (&zero, sizeof zero, 1, uptr->fileref) == 0 /* and write a zero to fill */ || fflush (uptr->fileref) != 0) /* the file to its capacity */ clearerr (uptr->fileref); /* clear and ignore any errors */ } } return result; /* return the result of the attach */ } /* 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. */ static 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; } /* 7906H/20H/25H disc bootstrap loader (12992H). The HP 1000 uses the 12992H boot loader ROM to bootstrap the ICD discs. Bit 12 of the S register determines whether an RPL or manual boot is performed. Bits 1-0 specify the head number to use. The loader reads 256 words from cylinder 0 sector 0 of the specified head into memory starting at location 2011 octal. Loader execution ends with one of the following instructions: * HLT 11 - the drive aborted the transfer due to an unrecoverable error * JSB 2055,I - the disc read succeeded The ICD drives are not supported on the 2100/14/15/16 CPUs, so no 21xx loader is provided. */ static const LOADER_ARRAY da_loaders = { { /* HP 21xx Loader does not exist */ IBL_NA, /* loader starting index */ IBL_NA, /* DMA index */ IBL_NA, /* FWA index */ { 0 } }, { /* HP 1000 Loader ROM (12992H) */ IBL_START, /* loader starting index */ IBL_DMA, /* DMA index */ IBL_FWA, /* FWA index */ { 0102501, /* 77700: START LIA 1 GET SWITCH REGISTER SETTING */ 0100044, /* 77701: LSL 4 SHIFT A LEFT 4 */ 0006111, /* 77702: CLE,SLB,RSS SR BIT 12 SET FOR MANUAL BOOT? */ 0100041, /* 77703: LSL 1 NO, SHIFT HEAD # FOR RPL BOOT */ 0001424, /* 77704: ALR,ALR SHIFT HEAD 2, CLEAR SIGN */ 0033744, /* 77705: IOR HDSEC SET EOI BIT */ 0073744, /* 77706: STA HDSEC PLACE IN COMMAND BUFFER */ 0017756, /* 77707: JSB BTCTL SEND DUMMY,U-CLR,PP */ 0102510, /* 77710: LIA IBI READ INPUT REGISTER */ 0101027, /* 77711: ASR 7 SHIFT DRIVE 0 RESPONSE TO LSB */ 0002011, /* 77712: SLA,RSS DID DRIVE 0 RESPOND? */ 0027710, /* 77713: JMP *-3 NO, GO LOOK AGAIN */ 0107700, /* 77714: CLC 0,C */ 0017756, /* 77715: JSB BTCTL SEND TALK, CL-RD,BUS HOLDER */ 0002300, /* 77716: CCE */ 0017756, /* 77717: JSB BTCTL TELL CARD TO LISTEN */ 0063776, /* 77720: LDA DMACW LOAD DMA CONTROL WORD */ 0102606, /* 77721: OTA 6 OUTPUT TO DCPC */ 0106702, /* 77722: CLC 2 READY DCPC */ 0063735, /* 77723: LDA ADDR1 LOAD DMA BUFFER ADDRESS */ 0102602, /* 77724: OTA 2 OUTPUT TO DCPC */ 0063740, /* 77725: LDA DMAWC LOAD DMA WORD COUNT */ 0102702, /* 77726: STC 2 READY DCPC */ 0102602, /* 77727: OTA 2 OUTPUT TO DCPC */ 0103706, /* 77730: STC 6,C START DCPC */ 0102206, /* 77731: TEST SFC 6 SKIP IF DMA NOT DONE */ 0117750, /* 77732: JSB ADDR2,I SUCCESSFUL END OF TRANSFER */ 0102310, /* 77733: SFS IBI SKIP IF DISC ABORTED TRANSFER */ 0027731, /* 77734: JMP TEST RECHECK FOR TRANSFER END */ 0102011, /* 77735: ADDR1 HLT 11B ERROR HALT */ 0000677, /* 77736: UNCLR OCT 677 UNLISTEN */ 0000737, /* 77737: OCT 737 UNTALK */ 0176624, /* 77740: DMAWC OCT 176624 UNIVERSAL CLEAR,LBO */ 0000440, /* 77741: LIST OCT 440 LISTEN BUS ADDRESS 0 */ 0000550, /* 77742: CMSEC OCT 550 SECONDARY GET COMMAND */ 0000000, /* 77743: BOOT OCT 0 COLD LOAD READ COMMAND */ 0001000, /* 77744: HDSEC OCT 1000 HEAD,SECTOR PLUS EOI */ 0000677, /* 77745: UNLST OCT 677 ATN,PRIMARY UNLISTEN,PARITY */ 0000500, /* 77746: TALK OCT 500 SEND READ DATA */ 0100740, /* 77747: RDSEC OCT 100740 SECONDARY READ DATA */ 0102055, /* 77750: ADDR2 OCT 102055 BOOT EXTENSION STARTING ADDRESS */ 0004003, /* 77751: CTLP OCT 4003 INT=LBO,T,CIC */ 0000047, /* 77752: OCT 47 PPE,L,T,CIC */ 0004003, /* 77753: OCT 4003 INT=LBO,T,CIC */ 0000413, /* 77754: OCT 413 ATN,P,L,CIC */ 0001015, /* 77755: OCT 1015 INT=EOI,P,L,CIC */ 0000000, /* 77756: BTCTL NOP */ 0107710, /* 77757: CLC IBI,C RESET IBI */ 0063751, /* 77760: BM LDA CTLP LOAD CONTROL WORD */ 0102610, /* 77761: OTA IBI OUTPUT TO CONTROL REGISTER */ 0102710, /* 77762: STC IBI RETURN IBI TO DATA MODE */ 0037760, /* 77763: ISZ BM INCREMENT CONTROL WORD POINTER */ 0002240, /* 77764: SEZ,CME */ 0127756, /* 77765: JMP BTCTL,I RETURN */ 0063736, /* 77766: LABL LDA UNCLR LOAD DATA WORD */ 0037766, /* 77767: ISZ LABL INCREMENT WORD POINTER */ 0102610, /* 77770: OTA IBI OUTPUT TO HPIB */ 0002021, /* 77771: SSA,RSS SKIP IF LAST WORD */ 0027766, /* 77772: JMP LABL GO BACK FOR NEXT WORD */ 0102310, /* 77773: SFS IBI SKIP IF LAST WORD SENT TO BUS */ 0027773, /* 77774: JMP *-1 RECHECK ACCEPTANCE */ 0027757, /* 77775: JMP BTCTL+1 */ 0000010, /* 77776: DMACW ABS IBI */ 0170100 } } /* 77777: ABS -START */ }; /* Device boot routine. This routine is called directly by the BOOT DA and LOAD DA commands to copy the device bootstrap into the upper 64 words of the logical address space. It is also called indirectly by a BOOT CPU or LOAD CPU command when the specified HP 1000 loader ROM socket contains a 12992H ROM. When called in response to a BOOT DA or LOAD DA command, the "unitno" parameter indicates the unit number specified in the BOOT command or is zero for the LOAD command, and "dptr" points at the DA device structure. The bootstrap supports loading only from the disc at bus address 0 only. The 12992F loader ROM will be copied into memory and configured for the DA select code. The S register will be set as it would be by the front-panel microcode. When called for a BOOT/LOAD CPU command, the "unitno" parameter indicates the select code to be used for configuration, and "dptr" will be NULL. As above, the 12992H loader ROM will be copied into memory and configured for the specified select code. The S register is assumed to be set correctly on entry and is not modified. The loader expects the S register to be is 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 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Bit 12 must be 1 for a manual boot. Bits 5-2 are nominally zero but are reserved for the target operating system. For example, RTE uses bit 5 to indicate whether a standard (0) or reconfiguration (1) boot is desired. 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 t_stat da_boot (int32 unitno, DEVICE *dptr) { static const HP_WORD da_preserved = 0000073u; /* S-register bits 5-3 and 1-0 are preserved */ static const HP_WORD da_manual_boot = 0010000u; /* S-register bit 12 set for a manual boot */ uint32 status; if (dptr == NULL) /* if we are being called for a BOOT/LOAD CPU */ status = cpu_copy_loader (da_loaders, unitno, /* then copy the boot loader to memory */ IBL_S_NOCLEAR, IBL_S_NOSET); /* but do not alter the S register */ else if (GET_BUSADR (da_unit [unitno].flags) != 0) /* otherwise BOOT DA is supported on bus address 0 only */ return SCPE_NOFNC; /* so reject other addresses as unsupported */ else /* otherwise this is a BOOT/LOAD DA */ status = cpu_copy_loader (da_loaders, da_dib.select_code, /* so copy the boot loader to memory */ da_preserved, da_manual_boot); /* and configure the S register if 1000 CPU */ if (status == 0) /* if the copy failed */ return SCPE_NOFNC; /* then reject the command */ else /* otherwise */ return SCPE_OK; /* the boot loader was successfully copied */ } /* 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. */ static 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 && not 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, TRACE_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, TRACE_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, TRACE_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, TRACE_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, TRACE_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, TRACE_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, TRACE_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, TRACE_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 through into the error_sink handler */ case error_sink: /* sinking data after an error */ if (TRACING (da_dev, TRACE_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, TRACE_XFER)) sprintf (action, "unhandled data %03o", data); break; } } if (accepted) tprintf (da_dev, TRACE_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 (not (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, TRACE_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 */ }