/* hp3000_cpp.c: HP 3000 Channel Program Processor simulator

   Copyright (c) 2023, 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.

   09-Jun-23    JDB     First release
   03-Jun-23    JDB     COLOSSUS passes with multiple drives
   15-Feb-23    JDB     CS80DIAG passes
   07-Feb-23    JDB     GICDIAG passes
   24-Nov-22    JDB     First MPE boot with CS/80 system disc
   18-May-22    JDB     Created

   References:
     - Series 64 Reference/Training Manual
         (30140-90005, April 1983)
     - Series 64/68/70 Computer Systems Microcode Manual
         (30140-90045, October 1986)
     - HP 3000 Series III CE Handbook
         (30000-90172, July 1981) [pp. 8-65 to 8-70]


   The Channel Program Processor is part of the standard microcode of HP-IB 3000
   systems, and a modified CPP is used with the HP 30341A HP-IB Interface Module
   ("Starfish"), where it resides on the processor card.  It assumes the
   function of the multiplexer and selector channels of the Series II and III,
   except that instead of having dedicated hardware to execute channel programs,
   dedicated firmware is used instead. The CPP gains control between machine
   instructions when the IMB CSRQ or IRQ signal is asserted.  When the CPP
   exits, either at the end of a channel program or when the program is waiting
   for a device event, such as a parallel poll response, CPU instruction
   execution resumes.

   The CPP simulator provides these global objects:

     uint32 cpp_request

       Count of CPU instructions to execute before entering the CPP for service.
       The value is set > 0 to indicate a request for CPP service after a delay
       represented by the value.  A value of 0 indicates that no CPP service is
       pending.

     t_bool cpp_cold_load

       Called by the CPU halt-mode interrupt handler to initiate a cold load on
       an HP-IB device in response to a LOAD command.  The load is performed in
       several stages, with the routine returning FALSE to wait for a device
       interrupt before reentering and TRUE when the load is complete and a
       cold-load trap can be taken.

     void cpp_service

       Called to service a channel service request or interrupt request from a
       device on the IMB.  Executes one or more channel cycles for the
       associated interface.  Used by the CPU to run the channel processor.

     IO_RESULT cpp_io_op

       Called to execute a channel I/O operation in response to a CPU
       instruction or a command issued to the IMBA.  Returns a structure
       containing the result condition code and read value if applicable.


   The CPP interacts with the Device Reference Table (DRT) entries for channel
   devices.  There is a 1:1 mapping between channel/device combinations and DRT
   entry numbers.  The device number = Channel * 8 + Device, and the DRT address
   is the device number times four.  So:

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   0   0   0   0   0 |    channel    |  device   | DEVNO
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   0   0   0 |    channel    |  device   | 0   0 | DRT address
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   The Device Reference Table contains one four-word entry for each device.  DRT
   entries are calculated from device 0 = address 0.  For the Series III, the
   first available device number is 4, so the DRT starts at %20.  For the HP-IB
   machines, channel 0 is reserved, so the DRT starts at %40, which corresponds
   to channel 1 device 0.

   The DRT entry layout for Starfish HP-IB peripherals is modified slightly from
   the Series 3x/4x format to accommodate the Series III interrupt microcode.
   Specifically, words 1 and 2 are interchanged, and the unused Series III word
   3 is redefined to hold the channel status, as follows:

     Word  Series 3x/4x HP-IB                Series III HP-IB
     ----  --------------------------------  --------------------------------
      0    Channel program absolute address  Channel program absolute address
      1    CPVA absolute address             Interrupt handler program label
      2    Interrupt handler program label   CPVA absolute address
      3    Channel program status            Channel program status

   The channel program status word is used by the microcode and has this format:

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | R | P | F | -   -   -   -   -   -   -   -   - | S | A | D | W |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     R = Channel program is running
     P = SIOP or HIOP instruction is pending
     F = Power fail recovery in progress
     S = Waiting for device status request
     A = Waiting for GIC FIFO to empty
     D = Waiting for DMA transfer to complete
     W = Waiting for PPR in WAIT instruction

   Only one of bits 12-15 may be set at any one time.

   The CPVA address points at a Channel Program Variable Area in bank 0 memory.
   It contains at least six words, formatted as follows:

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                  Channel Program Abort Code                   |  0
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                        Interrupt Code                         |  1
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                        Interrupt Code                         |  2
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                        Interrupt Code                         |  3
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                    DMA Abort Upper Address                    |  4
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                    DMA Abort Lower Address                    |  5
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Word 0 is used by the CPP to store status information after I/O channel
   aborts.  Any of words 0-3 may receive Interrupt instruction codes, although
   word 0 is recommended to be reserved for abort codes.  If a DMA transfer
   aborts, words 4 and 5 receive the address at which the abort occurred.  Any
   words that follow are not used by the CPP but may be used by the I/O driver.

   Channel program aborts are indicated in word 0 by the following codes:


   Interrupt Instruction

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1   0   0   0 |          Interrupt Instruction Code           |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   This code results from the execution of an Interrupt channel instruction.


   HIOP During Active Service

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1   0   1 | 0   0   0   0   0   0   0   0   0   0   0   0   0 |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   This format is used when an HIOP instruction is executed while the channel
   program is running free, i.e., not in the WAIT state.  In this case, the HIOP
   instruction will return CCG to indicate that an interrupt will occur when the
   program finally halts.  This code will accompany the interrupt.


   DMA Abort

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1   1   0 |               Register B Bits 0-12                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   A DMA abort stores the upper 13 bits of the DMA Status register (GIC Register
   B) in bits 3-15 of CPVA word 0, and sets CPVA words 4 and 5 to the abort
   address.


   Channel Program Execution Error

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1   1   1 | 0 | A | P | C | L | H | S | Q | M | N | T | D | I |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     A = Address rollover (non-DMA)
     P = Parity error detected on received bus command
     C = Status has changed
     L = Device Clear (DCL) universal command has been received
     H = Processor handshake aborted due to FIFO overflow/underflow
     S = Serial poll error
     Q = Illegal CSRQ
     M = Memory parity error
     N = Non-responding IMB module
     T = Channel hardware timeout
     D = Data chain error
     I = Invalid instruction

   This code is used to indicate channel execution errors detected by the CPP
   microcode.


   The CPP implements 16 channel instructions:

     Hex   Octal   Instruction
     ----  ------  ------------------------
     0000  000000  Relative Jump
     01xx  000400  Interrupt
     020x  001000  Wait
     03xx  001400  Read
     04x0  002000  Write
     05xx  002400  Device Specified Jump
     0600  003000  Identify
     07xx  003400  Read Control
     08xx  004000  Write Control
     09xx  004400  Clear
     0A0x  005000  Read-Modify-Write
     0B0x  005400  Read Register
     0C0x  006000  Write Register
     0D0x  006400  Command HP-IB
     0E00  007000  Execute DMA
     0Fxx  007400  Write Relative Immediate

   Instructions are typically two words in length using this format:

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |            opcode             |            options            |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                            operand                            |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   A few instructions use five words, and one uses a variable number between two
   and 258 words.  As with SIO I/O program instructions, CPP instructions are
   always located in bank 0 and an offset specified by the first word of the
   Device Reference Table entry.  The offset word is updated as the channel
   program executes.

   The layout of the instructions are as follows:


   Relative Jump

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   0   0   0   0 | -   -   -   -   -   -   -   - |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                         Displacement                          |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   The signed displacement is added to the address of the following word to
   determine the next instruction to fetch.


   Interrupt

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   0   0   0   1 | H | -   -   -   -   - | CPVA  |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0 |                Interrupt Code                 |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     H = Halt / Run (1/0)

   If the halt bit is set, the CPP stops the program and exits to execute the
   next machine instruction.  Otherwise, the CPP fetches the next channel
   instruction and continues executing.  The CPVA field specifies the CPVA word
   number in which to store the interrupt code in bits 4-15; bits 0-3 of the
   CPVA word will be set to 1000.


   Wait

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   / | 0   0   0   0   0   0   1   0 | -   -   -   -   -   -   - | S | Series III/3x
   | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   \ | 0   0   0   0   0   0   1   0 | -   -   -   -   - | S | CPVA  | Series 4x/5x/6x
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0 |     Serial Poll Response / Interrupt Code     |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     S = Wait for selected conditions

   This instruction causes a suspension of the channel program execution until
   the device again requests service, allowing the channel to start polling all
   of the devices.


   Read

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   0   0   1   1 |  Data Chain   |   Modifier    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                  Byte Count / Residue Count                   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |   Termination Displacement    |         Burst Length          |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | B | R | L | - | N | U | -   - |    Extended Memory Address    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Memory Address / Residue Address                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     B = Record / Burst mode (0/1)
     R = Start with Left / Right byte (0/1)
     L = Terminate on LF (Read)
     N = No memory address increment
     U = Do not update instruction words after execution

   The Modifier is sent as a secondary address in the range 00-0F after the
   device is addressed with the Talk command.

   Transfers can be made in Record Mode or Burst Mode.  Record Mode sets up a
   transfer for the full Byte Count.  Burst Mode transfers a count of bytes
   corresponding to the smaller of the Burst Length and the Byte Count; a Burst
   Length of zero is interpreted as a 256-byte burst.  Burst Mode generally
   requires multiple executions to transfer the full Byte Count, and the device
   is unaddressed between bursts to free the bus.

   Read transactions always terminate if a byte tagged with EOI is received.
   Setting the L bit also terminates a transaction if a line feed byte is
   received.

   DMA is used for all transfers except Burst Mode with Burst Length = 1.

   The N bit determines whether all transfers are made to the same address, or
   whether the address is incremented after each word is transferred.

   Unless the U (no update) bit is set in the instruction, the byte count in the
   second word, the R (left/right) bit in word 4, and the memory address in word
   5 are updated and written back into the instruction after each transfer.

   The Data Chain field is used for data chaining.  If the field in the initial
   instruction is zero, then the Byte Count specifies the entire transfer
   length.  If the field is a non-zero value N, then the instruction is the
   first of N-1 consecutive instructions that collectively form a single bus
   transaction, with each succeeding instruction's field indicating the number
   of remaining fields that follow.  This allows subsets of the full transfer
   length to be written to different memory addresses.

   Chained Record Mode transfers are performed consecutively as a single bus
   transaction.  DMA completion of the current chained transfer is immediately
   followed by the initiation of the next chained transfer.  The device is
   addressed at the start of the chained sequence and unaddressed at the end of
   the sequence.  Receipt of a byte tagged with EOI terminates the entire
   transaction, regardless of where in the chained sequence it occurs.

   Chained Burst Mode transfers perform one burst per execution, and the device
   is addressed only for the duration of the burst.  Receipt of a byte tagged
   with EOI during a burst skips any remaining chained instructions and
   terminates the transaction.  Otherwise, after the current burst is
   transferred, and regardless of whether that transfer exhausts the Byte Count
   for the current intermediate chain entry, any remaining chained instructions
   in the sequence are skipped, and channel program execution resumes after the
   final chained entry.  The sequence must be executed in a loop to perform the
   entire transaction.  As each chained entry's Byte Count is exhausted, bursts
   continue with the succeeding entry.  The transaction concludes when the final
   chain entry's Byte Count is exhausted or when EOI is received.

   Execution of an instruction or chained instruction sequence ends with control
   transferring to one of three locations.  If the transaction ends with receipt
   of a data byte tagged with EOI, then execution continues with the next
   instruction following the sequence.  If a Burst Mode transfer ends with a
   remaining Byte Count, then the next two-word instruction is skipped, and
   execution continues with the following instruction.  If the transaction ends
   with Byte Count exhaustion, then the Termination Displacement field is added
   to the address of the next instruction to determine where execution
   continues.  In tabular form, where * represents the first location following
   the instruction or instruction sequence:

     Location  Reason for Read Termination
     --------  -----------------------------------------
      * + 0    End of transaction on EOI receipt
      * + 2    End of burst transfer but not transaction
      * + TD   End of transaction on Byte Count

   An end-of-burst exit is typically followed by a WAIT for a poll response, a
   DSJ to confirm success of the transfer, and then a JUMP back to the start of
   the instruction sequence.  The loop continues until the end-of-transaction
   exit occurs.


   Write

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   0   1   0   0 |  Data Chain   |   Modifier    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                  Byte Count / Residue Count                   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   -   -   - |         Burst Length          |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | B | R | E | - | N | U | -   - |    Extended Memory Address    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Memory Address / Residue Address                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     B = Record / Burst mode (0/1)
     R = Start with Left / Right byte (0/1)
     E = Do not tag last byte of burst with EOI
     N = No memory address increment
     U = Do not update instruction words after execution

   The Modifier is sent as a secondary address in the range 00-0F after the
   device is addressed with the Listen command.

   Transfers can be made in Record Mode or Burst Mode.  Record Mode sets up a
   transfer for the full Byte Count.  Burst Mode transfers a count of bytes
   corresponding to the smaller of the Burst Length and the Byte Count; a Burst
   Length of zero is interpreted as a length of 256 bytes.  Burst Mode generally
   requires multiple executions to transfer the full Byte Count, and the device
   is unaddressed between bursts to free the bus.

   The E (EOI) bit is ignored for Record Mode transfers; the final byte is
   always tagged with EOI. For Burst Mode transfers, the E bit specifies whether
   the final byte of each intermediate burst is tagged with EOI. Regardless of
   the E bit, the final byte of the final burst is always tagged.

   DMA is used for all transfers except Burst Mode with Burst Length = 1.  For
   the latter, E = 0 tags the byte with EOI, whereas E = 1 tags the byte with
   EOI only if it is the last byte of the transaction (i.e., Byte Count = 0).

   The N bit determines whether all transfers are made from the same address, or
   whether the address is incremented after each word is transferred.

   Unless the U (no update) bit is set in the instruction, the byte count in the
   second word, the R (left/right) bit in word 4, and the memory address in word
   5 are updated and written back into the instruction after each transfer.

   The Data Chain field is used for data chaining.  If the field in the initial
   instruction is zero, then the Byte Count specifies the entire transfer
   length.  If the field is a non-zero value N, then the instruction is the
   first of N-1 consecutive instructions that collectively form a single bus
   transaction, with each succeeding instruction's field indicating the number
   of remaining fields that follow.  This allows subsets of the full transfer
   length to originate from different memory addresses.

   Chained Record Mode transfers are performed consecutively as a single bus
   transaction.  DMA completion of the current chained transfer is immediately
   followed by the initiation of the next chained transfer.  The device is
   addressed at the start of the chained sequence and unaddressed at the end of
   the sequence.  EOI tags the final byte of the final transfer.  After all
   transfers in the chained instruction sequence are made, channel program
   execution continues with the next instruction following the sequence.

   Chained Burst Mode transfers perform one burst per execution, and the device
   is addressed only for the duration of the burst.  After the current burst is
   transferred, and regardless of whether that transfer exhausts the Byte Count
   for the current intermediate chain entry. any remaining chained instructions
   in the sequence are skipped, and channel program execution resumes after the
   final chained entry.  The sequence must be executed in a loop to perform the
   entire transaction.  As each chained entry's Byte Count is exhausted, bursts
   continue with the succeeding entry.  The transaction concludes only when the
   final chain entry's Byte Count is exhausted.

   Execution of a Burst Mode instruction or chained instruction sequence ends
   with control transferring to one of two locations.  If the transaction ends
   with Byte Count exhaustion, then execution continues with the next
   instruction following the sequence.  If the transfer ends with a remaining
   Byte Count, then the next two-word instruction is skipped, and execution
   continues with the following instruction.  In tabular form, where *
   represents the first location following the instruction or instruction
   sequence:

     Location  Reason for Write Termination
     --------  -----------------------------------------
     * + 0     End of transaction
     * + 2     End of burst transfer but not transaction

   An end-of-burst exit is typically followed by a WAIT for a poll response, a
   DSJ to confirm success of the transfer, and then a JUMP back to the start of
   the instruction sequence.  The loop continues until the end-of-transaction
   exit occurs.


   Device Specified Jump

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   0   1   0   1 |     Maximum Response (N)      |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   -   -   - |         Returned Byte         |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Relative Displacement for Byte 0                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Relative Displacement for Byte 1                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                              ,,,                              |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Relative Displacement for Byte N                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   This instruction has a variable number of words.  If the DSJ byte returned
   from the device is greater than the number of displacement entries, the
   instruction following this one is executed.  Otherwise, the signed
   displacement corresponding to the DSJ value is added to the address of the
   word following this instruction to determine the next instruction to fetch.

   The upper byte of the operand word is set to zero by the microcode when the
   lower byte is written.  Note also that the microcode appears to assume that
   the byte is returned immediately, as it does not suspend to wait for the
   inbound FIFO to fill.  This will not work with our Amigo and CS/80
   implementations, which insert a controller delay between command reception
   and byte return.


   Identify

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   0   1   1   0 | -   -   -   -   -   -   -   - |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |      First Returned Byte      |     Second Returned Byte      |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Send an Amigo Identify command to the device.  Two ID bytes are obtained from
   the device and returned in the second word of the instruction.


   Read Control

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   0   1   1   1 |  Data Chain   |   Modifier    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                  Byte Count / Residue Count                   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |   Termination Displacement    |         Burst Length          |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | B | R | L | - | N | U | -   - |    Extended Memory Address    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Memory Address / Residue Address                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     B = Record / Burst mode (0/1)
     R = Start with Left / Right byte (0/1)
     L = Terminate on LF (Read)
     N = No memory address increment
     U = Do not update instruction words after execution

   The Modifier is sent as a secondary address in the range 10-1F after the
   device is addressed with the Talk command.  Otherwise, this command is
   identical in operation to the Read command.


   Write Control

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   0   0   0 |  Data Chain   |   Modifier    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                  Byte Count / Residue Count                   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   -   -   - |         Burst Length          |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | B | R | E | - | N | U | -   - |    Extended Memory Address    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Memory Address / Residue Address                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     B = Record / Burst mode (0/1)
     R = Start with Left / Right byte (0/1)
     E = Do not tag last byte with EOI
     N = No memory address increment
     U = Do not update instruction words after execution

   The Modifier is sent as a secondary address in the range 10-1F after the
   device is addressed with the Listen command.  Otherwise, this command is
   identical in operation to the Write command.


   Clear

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   0   0   1 |         Control Byte          |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   - |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Sends an Amigo Clear sequence to the channel.


   Read-Modify-Write

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   0   1   0 | -   -   -   - |   Register    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   -   -   -   -   -   - | S |  Bit Number   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     S = Set/Clear bit (1/0)

   An I/O Read command is issued for the register number in bits 12-15 of the
   opcode word.  The bit specified by bits 12-15 of the operand word is set or
   cleared as directed by bit 11 of the operand.  An I/O Write command is then
   issued to write the updated register value back.


   Read Register

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   0   1   1 | -   -   -   - |   Register    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                         Returned Word                         |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   An I/O Read command is issued for the register number in bits 12-15 of the
   opcode word.  The value returned is stored in the operand word.


   Write Register

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   1   0   0 | -   -   -   - |   Register    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                         Supplied Word                         |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   An I/O Write command is issued for the register number in bits 12-15 of
   the opcode word.  The value to write is presented on the data bus.


   Command HP-IB

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   1   0   1 | -   -   -   -  -  |   Count   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |           Command 1           |           Command 2           |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |           Command 3           |           Command 4           |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |           Command 5           |           Command 6           |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |           Command 7           |           Command 8           |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     Count = Count of commands (0 = eight commands)

   If the outbound FIFO is not empty, then suspend.  Otherwise, do an I/O Write
   to register 0 for each byte specified by the count, with bits 0-1 set to 01
   to have the PHI assert ATN with each byte.


   Execute DMA

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   1   1   0 | -   -   -   -   -   -   -   - |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                  Byte Count / Residue Count                   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |   Termination Displacement    |         Burst Length          |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | B | R | T | D | N | U | -   - |    Extended Memory Address    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Memory Address / Residue Address                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     B = Record / Burst mode (0/1)
     R = Start with Left / Right byte (0/1)
     T = Terminate on LF (Read) / Do not tag EOI (Write)
     D = DMA direction (0/1 = inbound/outbound)
     N = No memory address increment
     U = Do not update instruction words after execution

   This command is identical to the Read and Write commands, except that data
   chaining is not supported, and no HP-IB commands (such as Talk and Listen)
   are sent.  This command is used by diagnostics to send and receive "bare"
   data when verifying bus transmission between two GICs.

   A burst-mode read can end either on reception of a byte tagged with EOI, on
   the burst count, or optionally on reception of a line feed if the T bit is
   set.  A record-mode read terminates only on EOI; the T bit is ignored.

   Execution of a DMA Read instruction ends with control transferring to one of
   three locations.  If the transaction ends with receipt of a data byte tagged
   with EOI, then execution continues with the next instruction following the
   sequence.  If a Burst Mode transfer ends with a remaining Byte Count, then
   the next two-word instruction is skipped, and execution continues with the
   following instruction.  If the transaction ends with Byte Count exhaustion,
   then the Termination Displacement field is added to the address of the next
   instruction to determine where execution continues.

   Execution of DMA Write instruction ends with control transferring to one of
   two locations.  If the transaction ends with Byte Count exhaustion, then
   execution continues with the next instruction following the sequence.  If the
   transfer ends with a remaining Byte Count, then the next two-word instruction
   is skipped, and execution continues with the following instruction.

   In tabular form, where * represents the first location following the
   instruction:

     Location  Reason for Read Termination
     --------  -----------------------------------------
      * + 0    End of transaction on EOI receipt
      * + 2    End of burst transfer but not transaction
      * + TD   End of transaction on Byte Count

     Location  Reason for Write Termination
     --------  -----------------------------------------
     * + 0     End of transaction
     * + 2     End of burst transfer but not transaction


   Write Relative Immediate

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   1   1   1 |         Displacement          |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                             Data                              |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   The signed displacement (-128..127) is added to the address of the following
   word to determine the location to which the operand word is written.


   CRC Initialize

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   1   0   0   0   0 | -   -   -   -   -   -   -   - |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   - |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   CRC Compare

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   1   0   0   0   1 | H | I | -   -   -   - | CPVA  |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   - |                Interrupt Code                 |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     H = Halt / Run (1/0)
     I = Interrupt on CRC match

   These commands test whether the channel has CRC capability by reading
   register 7 and seeing if bit 0 = 1.  The PHI does not have CRC capability,
   so these commands are not implemented.


   For the Starfish, the CPP performs two additional functions over those in
   common with the HP-IB machines.  First, it executes this modified subset of
   the HP-IB I/O instructions:

     Inst  Opcode  Action
     ----  ------  -----------------------------
     SIOP  000000  Start I/O Program
     HIOP  000001  Halt I/O Program
     RIOC  000002  Read I/O Channel
     WIOC  000003  Write I/O Channel
     SED2  000004  Enable/Disable CPP interrupts
     IOCL  000005  I/O Clear
     INIT  000006  Initialize I/O Channel
     SLFT  000007  Initiate self-test

   The SED2 and SLFT instructions are specific to the Starfish.  The others are
   standard HP-IB machine instructions listed in the Machine Instruction Set
   manual.

   Second, it establishes communication between the Series III and the CPP
   through the 30340A Intermodule Bus Adapter.  The above instructions are
   executed by SIO instructions directed at the IMBA and specifying an SIO
   channel program address of -1 (177777 octal).  This causes the CPP to examine
   a "mailbox" consisting of the Device Reference Table entries for devices 126
   and 127, which are dedicated to the Series III multiplexer channels but are
   otherwise unused. The eight-word mailbox resides at memory addresses 770-777
   octal. Interrupt requests from the channel program are reflected back through
   the IMBA to the Series III.

   Mailbox locations are assigned the following meanings:

     Box  Location  Use
     ---  --------  --------------------------------------------------------
      0     770     Instruction Opcode (0-7)
      1     771     DRT or channel number or Enable/Disable interrupts (SED)
                      or IMB Command (RIOC/WIOC)
      2     772     Data Read (RIOC) or Written (WIOC)
      3     773     Channel Program Pointer
      4     774     Operation Status
      5     775     (not used)
      6     776     (not used)
      7     777     (not used)

   The operation status word in mailbox 4 has this format after an SIO
   instruction:

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | F | P | T | M | O | D | H | W | S | C | V | -   -   -   - | E |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     F = Operation is complete (idle)
     P = IMB parity error
     T = Invalid timer interrupt
     M = Non-responding module timeout
     O = Invalid mailbox opcode
     D = SIO disabled flag during SIOP, RIOC, or WIOC
     H = SIOP failed because previous channel program is not halted
     W = SIOP or HIOP failure - halt pending but not in WAIT
     S = INIT failed - unable to bring system controller on-line
     C = INIT failed - GIC not system controller
     V = Data not valid
     E = Any error (logical OR of the other error bits)

   After executing the SIO program, the Series III waits for bit 0 to set.  This
   indicates that the CPP has recognized the CSRQ and has begun execution of the
   HP-IB machine instruction.  If bit 15 is set, then the other bits indicate
   the cause of the error.


   Implementation notes:

    1. This implementation of the CPP follows the Series 6x microcode closely.

    2. The Series 6x microcode defines the CRC Initialize and CRC Compare
       channel instructions.  These require a channel controller that uses an
       ABI chip.  As the GIC uses the PHI chip, which does not support CRC in
       hardware, the simulator recognizes these instructions but does not
       implement them.

    3. In hardware, the Starfish processor may not respond immediately if it is
       busy or has a fault.  Therefore, MPE imposes a time limit in the wait
       loop for status bit 0 to set.  Under simulation, the CPP always responds
       to the CSRQ from the IMBA and so always sets bit 0 at the completion of
       the simulated machine instruction.  As a result, MPE essentially sees the
       bit set immediately and so can never time out.

    4. When asserting IFC, the CPP microcode executes an internal wait loop
       before denying IFC to ensure that the signal remains asserted for the
       100 microsecond minimum specified by the IEEE-488 standard.  In
       simulation, device modules respond to IFC assertion; the duration of the
       signal is irrelevant.  Therefore, we assert and then immediately deny
       IFC, rather than attempting to replicate the delay by saving the
       assertion point, setting up an event timer, exiting, and then reentering
       and resuming at the prior point of suspension.  The latter operation is
       problematic, as CPP service could be reentered for a different channel
       card while the IFC timer is active.  This cannot happen in microcode,
       because the timing loop does not yield.

    5. MPE V/E allows the DRT to reside at a location specified by the DRT bank
       and offset, which are located at absolute memory locations %10 and %11,
       respectively.  Pre-V/E restricted the DRT to locations %40-%777.  We
       follow that restriction here.
*/



#include "hp3000_defs.h"
#include "hp3000_cpu.h"
#include "hp3000_cpu_ims.h"
#include "hp3000_io.h"
#include "hp3000_imb.h"                         /* must be included after hp3000_io.h */
#include "hp3000_mem.h"
#include "hp3000_gic.h"



/* Program constants.


   Implementation notes:

    1. For the Starfish, the interrupt mask word appears to be assigned to
       memory location %13, which is the location used by the Series II for the
       second CPU mask word.  This would make sense for the Series III, which
       cannot have a second CPU, except that the MPE's INITIAL module uses
       location %13 for a temporary location (stack DB).  The HP-IB machines use
       location 7, so we do too.  It's actually irrelevant, as MPE V/R never
       issues a SMSK or RMSK instruction.
*/

#define RESERVED_MEMORY     0000040u            /* first address beyond memory reserved for MPE */

#define MBX0                0000770u            /* Mailbox 0 memory location */
#define MBX1                0000771u            /* Mailbox 1 memory location */
#define MBX2                0000772u            /* Mailbox 2 memory location */
#define MBX3                0000773u            /* Mailbox 3 memory location */
#define MBX4                0000774u            /* Mailbox 4 memory location */

#define IMBA_CHANNEL        1                   /* IMBA channel assignment (fixed) */

#define Completion_Unit     cpp_unit [0]        /* IMBA processor completion unit */
#define Timeout_Unit        cpp_unit [1]        /* cold load timeout unit */


/* Common per-unit state variables */

#define Mailbox_Status      Completion_Unit.u3  /* IMBA mailbox status word */

#define Loader_State        Timeout_Unit.u3     /* cold load state word */
#define Loader_Device       Timeout_Unit.u4     /* cold load device number */


/* Mailbox 4.

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | F | P | T | M | O | D | H | W | S | C | V | -   -   -   - | E |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define MBX_COMPLETE        HP_BIT  (0)         /* (F) Instruction is complete (CPP idle) */
#define MBX_PARITY          HP_BIT  (1)         /* (P) Parity error on IMB */
#define MBX_TIMER           HP_BIT  (2)         /* (T) Invalid timer interrupt */
#define MBX_MODULE          HP_BIT  (3)         /* (M) Non-responding module timeout */
#define MBX_OPCODE          HP_BIT  (4)         /* (O) Invalid instruction opcode */
#define MBX_DISABLED        HP_BIT  (5)         /* (D) SIO disabled flag detected during SIOP, RIOC, or WIOC */
#define MBX_RUNNING         HP_BIT  (6)         /* (H) SIOP failed because previous channel program is not halted */
#define MBX_PENDING         HP_BIT  (7)         /* (W) SIOP or HIOP failed because halt is pending but not in WAIT */
#define MBX_ONLINE          HP_BIT  (8)         /* (S) INIT failed because unable to bring system controller on-line */
#define MBX_SYSCNTLR        HP_BIT  (9)         /* (C) INIT failed because GIC not system controller */
#define MBX_DNV             HP_BIT (10)         /* (V) Data not valid on IMB */
#define MBX_ERROR           HP_BIT (15)         /* (E) Error in one or more bits */


/* PHI commands */

#define PHI_UNLISTEN        (R0_MODE_COMMAND | BUS_UNLISTEN)
#define PHI_UNTALK          (R0_MODE_COMMAND | BUS_UNTALK)

#define PHI_LISTEN          (R0_MODE_COMMAND | BUS_LISTEN)
#define PHI_TALK            (R0_MODE_COMMAND | BUS_TALK)
#define PHI_SECONDARY       (R0_MODE_COMMAND | BUS_SECONDARY)

#define PHI_DCL             (R0_MODE_COMMAND | BUS_DCL) /* Universal Device Clear */
#define PHI_SDC             (R0_MODE_COMMAND | BUS_SDC) /* Selected Device Clear */
#define PHI_SPD             (R0_MODE_COMMAND | BUS_SPD) /* Serial Poll Disable */


/* Program delays.


   Implementation notes:

    1. The bus delay to assert CSRQ must be long enough for both Amigo Identify
       bytes to be sourced to the bus.  The CPP Identify command is reentered
       when the FIFO data available interrupt occurs, which happens when the
       first byte is sourced.  There is no further occupancy check made, and the
       bytes are read from the FIFO in sequence.  If the delay is insufficient,
       a FIFO underrun error will occur.
*/

#define CPP_DELAY           uS (5)              /* CPP entry delay */
#define BUS_DELAY           uS (5)              /* delay to recognize CSRQ or IRQ on the bus */
#define RESTART_DELAY       uS (500)            /* delay to restart execution after limit reached */
#define SELF_TEST_DELAY     mS (1)              /* self test execution time */
#define AMIGO_ID_DELAY      mS (1)              /* timeout for Identify response */
#define COLD_LOAD_DELAY     S (1)               /* completion timeout for cold load media reads */


/* Channel program status word (DRT word 3).

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | R | P | F | -   -   -   -   -   -   -   -   - | S | A | D | W |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CPS_RUNNING         HP_BIT  (0)         /* (R) channel program is running */
#define CPS_PENDING         HP_BIT  (1)         /* (P) new channel status is pending */
#define CPS_POWERFAIL       HP_BIT  (2)         /* (F) power fail recovery is in progress */
#define CPS_STATUSWAIT      HP_BIT (12)         /* (S) waiting for device status request */
#define CPS_FIFOWAIT        HP_BIT (13)         /* (A) waiting for GIC FIFO to receive data */
#define CPS_DMAWAIT         HP_BIT (14)         /* (D) waiting for DMA transfer to complete */
#define CPS_PROGWAIT        HP_BIT (15)         /* (W) channel program is in wait state */

#define CPS_STATE_MASK      HP_BITS ( 0, 2)     /* channel program state mask */
#define CPS_EXEC_MASK       HP_BITS ( 0, 1)     /* execution status mask */
#define CPS_WAIT_MASK       HP_BITS (12, 14)    /* hardware wait status mask */
#define CPS_NOWAIT          0u                  /* no hardware wait bits */

#define CPS_STOPPED         0000000u                    /* channel is stopped */
#define CPS_STARTING        (CPS_RUNNING | CPS_PENDING) /* channel is starting */
#define CPS_STOPPING        (CPS_STOPPED | CPS_PENDING) /* channel is stopping */


/* Channel program abort words (CPVA word 0) */

/* Interrupt Instruction

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1   0   0 | 0 |          Interrupt Instruction Code           |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CPA_INTERRUPT       TO_HP_BITS (0, 2, 4)        /* Interrupt abort code */

#define TO_CPA_INTERRUPT(w) (CPA_INTERRUPT | TO_HP_BITS (4, 15, w))


/* HIOP During Active Service

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1   0   1 | 0   0   0   0   0   0   0   0   0   0   0   0   0 |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CPA_HIOP            TO_HP_BITS (0, 2, 5)        /* HIOP abort code */


/* DMA Abort

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1   1   0 |               Register B Bits 0-12                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CPA_DMA             TO_HP_BITS (0, 2, 6)        /* DMA abort code */

#define TO_CPA_DMA_ABORT(w) (CPA_DMA | RB_ABORT (w))


/* Channel Program Execution Error

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1   1   1 | 0 | A | P | C | L | H | S | Q | M | N | T | D | I |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   An Illegal CSRQ is handled in one of two ways.  Depending on the state of the
   CPP when the abort occurs, the abort may require PHI cleanup before halting
   the channel program.  The abort_program routine is passed either CPA_ILLEGAL
   if the PHI must be cleaned up or CPA_NCILLEGAL if no cleanup is necessary.
   We arbitrarily use the sign bit of the code to indicate the latter.
*/

#define CPA_EXEC            TO_HP_BITS (0, 2, 7)        /* Program Execution abort code */

#define CPA_ROLLOVER        (CPA_EXEC | HP_BIT  (4))    /* (A) Address rollover (non-DMA) */
#define CPA_BPARITY         (CPA_EXEC | HP_BIT  (5))    /* (P) Parity error detected on received bus command */
#define CPA_CHANGE          (CPA_EXEC | HP_BIT  (6))    /* (C) Status has changed */
#define CPA_DCL             (CPA_EXEC | HP_BIT  (7))    /* (L) Device Clear (DCL) command has been received */
#define CPA_FIFO            (CPA_EXEC | HP_BIT  (8))    /* (H) Handshake abort due to FIFO overflow/underflow */
#define CPA_POLL            (CPA_EXEC | HP_BIT  (9))    /* (S) Serial poll error */
#define CPA_ILLEGAL         (CPA_EXEC | HP_BIT (10))    /* (Q) Illegal CSRQ */
#define CPA_MPARITY         (CPA_EXEC | HP_BIT (11))    /* (M) Memory parity error */
#define CPA_NORESPONSE      (CPA_EXEC | HP_BIT (12))    /* (N) Non-responding IMB module */
#define CPA_TIMEOUT         (CPA_EXEC | HP_BIT (13))    /* (T) Channel hardware timeout */
#define CPA_CHAIN           (CPA_EXEC | HP_BIT (14))    /* (D) Data chain error */
#define CPA_INVALID         (CPA_EXEC | HP_BIT (15))    /* (I) Invalid instruction */

#define CPA_NOCLEANUP       D32_SIGN                    /* no-bus-cleanup abort option */
#define CPA_NCILLEGAL       (CPA_NOCLEANUP | CPA_ILLEGAL)


/* Starfish instructions */

static const char *starfish_names [] = {        /* Starfish instruction names, indexed by opcode */
    "SIOP",                                     /*   0 = Start I/O Program */
    "HIOP",                                     /*   1 = Halt I/O Program */
    "RIOC",                                     /*   2 = Read I/O Channel */
    "WIOC",                                     /*   3 = Write I/O Channel */
    "SED2",                                     /*   4 = Enable/Disable CPP interrupts */
    "IOCL",                                     /*   5 = I/O Clear */
    "INIT",                                     /*   6 = Initialize I/O Channel */
    "SLFT"                                      /*   7 = Initiate self-test */
    };


/* Channel program instructions.

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |      instruction opcode       |            options            |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                            operand                            |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+


   Implementation notes:

    1. The channel program word count table provides the number of words for
       each channel program instruction.  All instructions are fixed-length,
       except for the Device Specified Jump instruction that depends on the
       count of potential return values.  The DSJ entry therefore is set to
       three words, which is the minimum length; the actual length is computed
       at run-time as 3 + N, where N is the count of return values.
*/

typedef enum {                                  /* channel program opcodes */
    Relative_Jump = 0,
    Interrupt,
    Wait,
    Read,
    Write,
    Device_Specified_Jump,
    Identify,
    Read_Control,
    Write_Control,
    Clear,
    Read_Modify_Write,
    Read_Register,
    Write_Register,
    Command_HPIB,
    Execute_DMA,
    Write_Relative_Immediate,
    CRC_Initialize,
    CRC_Compare,
    Invalid
    } CP_OPCODE;

#define CPI_OPCODE(w)       ((CP_OPCODE) ((w) >= TO_HP_BITS (0, 7, Invalid) \
                                           ? Invalid : HP_BITS_TO (0, 7, w)))

static const uint8 word_count [] = {            /* instruction words required, indexed by CP_OPCODE */
    2, 2, 2, 5, 5, 3, 2, 5,                     /*   Relative Jump through Read Control */
    5, 2, 2, 2, 2, 5, 5, 2,                     /*   Write Control through Write Relative Immediate */
    2, 2,                                       /*   CRC Initialize through CRC Compare */
    0                                           /*   Invalid */
    };

static const char *instruction_names [] = {     /* instruction names, indexed by CP_OPCODE */
    "Relative Jump",
    "Interrupt",
    "Wait",
    "Read",
    "Write",
    "Device Specified Jump",
    "Identify",
    "Read Control",
    "Write Control",
    "Clear",
    "Read-Modify-Write",
    "Read Register",
    "Write Register",
    "Command HP-IB",
    "Execute DMA",
    "Write Relative Immediate",
    "CRC Initialize",
    "CRC Compare",
    "(invalid) "                                /* has a space to separate it from the octal value */
    };

static const BITSET_NAME wait_names [] = {      /* Wait instruction option names */
    "CRC comparison",                           /*   bit  8 */
    "CRC initialization",                       /*   bit  9 */
    NULL,                                       /*   bit 10 */
    NULL,                                       /*   bit 11 */
    NULL,                                       /*   bit 12 */
    NULL,                                       /*   bit 13 */
    NULL,                                       /*   bit 14 */
    "wait for condition"                        /*   bit 15 */
    };

static const BITSET_FORMAT wait_format =        /* names, offset, direction, alternates, bar */
    { FMT_INIT (wait_names, 0, msb_first, no_alt, append_bar) };


static const BITSET_NAME dma_read_names [] = {  /* Read instruction option names */
    "\1burst mode\0record mode",                /*   bit  0 */
    "\1right byte\0left byte",                  /*   bit  1 */
    "LF terminate",                             /*   bit  2 */
    NULL,                                       /*   bit  3 */
    "no increment",                             /*   bit  4 */
    "no update"                                 /*   bit  5 */
    };

static const BITSET_FORMAT dma_read_format =    /* names, offset, direction, alternates, bar */
    { FMT_INIT (dma_read_names, 10, msb_first, has_alt, no_bar) };


static const BITSET_NAME dma_write_names [] = { /* Write instruction option names */
    "\1burst mode\0record mode",                /*   bit  0 */
    "\1right byte\0left byte",                  /*   bit  1 */
    "no EOI",                                   /*   bit  2 */
    NULL,                                       /*   bit  3 */
    "no increment",                             /*   bit  4 */
    "no update"                                 /*   bit  5 */
    };

static const BITSET_FORMAT dma_write_format =   /* names, offset, direction, alternates, bar */
    { FMT_INIT (dma_write_names, 10, msb_first, has_alt, no_bar) };


/* Channel program instruction accessors */


/* Interrupt

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   0   0   0   1 | H | -   -   -   -   - | CPVA  |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0 |                Interrupt Code                 |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CPI_HALT            HP_BIT (8)          /* (H) halt */

#define CPI_CPVA(w)         HP_BITS_TO (14, 15, w)
#define CPI_CODE(w)         HP_BITS_TO ( 4, 15, w)


/* Wait

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   / | 0   0   0   0   0   0   1   0 | -   -   -   -   -   -   - | S | Series III/3x
   | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   \ | 0   0   0   0   0   0   1   0 | -   -   -   -   - | S | CPVA  | Series 4x/5x/6x
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0 |     Serial Poll Response / Interrupt Code     |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CPI_WAIT            HP_BIT (15)         /* (S) wait for selected conditions (Series III/3x) */


/* Read

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   0   0   1   1 |  Data Chain   |   Modifier    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                  Byte Count / Residue Count                   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |   Termination Displacement    |         Burst Length          |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | B | R | L | D | N | U | -   - |    Extended Memory Address    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Memory Address / Residue Address                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CPI_BURST_MODE      HP_BIT  (0)         /* (B) Record / Burst mode (0/1) */
#define CPI_RIGHT_BYTE      HP_BIT  (1)         /* (R) Start with Left / Right byte (0/1) */
#define CPI_TERM_LF         HP_BIT  (2)         /* (L) Terminate on LF (Read) */
#define CPI_DIRECTION       HP_BIT  (3)         /* (D) DMA direction (0 = inbound) */
#define CPI_NO_INCREMENT    HP_BIT  (4)         /* (N) No memory address increment */
#define CPI_NO_UPDATE       HP_BIT  (5)         /* (U) Do not update instruction words after execution */

#define CPI_CHAIN_LSB       HP_BIT (11)         /* LSB of the data chain field */

#define CPI_CHAIN_MASK      HP_BITS (8, 11)     /* data chain mask */
#define CPI_EX_ADDR         HP_BITS (8, 15)     /* extended address mask */

#define CPI_DATA_CHAIN(w)   HP_BITS_TO ( 8, 11, w)
#define CPI_MODIFIER(w)     HP_BITS_TO (12, 15, w)
#define CPI_COUNT(w)        HP_BITS_TO ( 0, 15, w)
#define CPI_TERM_DISP(w)    HP_BITS_TO ( 0,  7, w)
#define CPI_BURST_LEN(w)    HP_BITS_TO ( 8, 15, w)
#define CPI_DMA_FLAGS(w)    HP_BITS_TO ( 0,  3, w)      /* (B), (R), (L), and (D) flags */
#define CPI_BANK(w)         HP_BITS_TO ( 8, 15, w)
#define CPI_OFFSET(w)       HP_BITS_TO ( 0, 15, w)

#define CPI_TO_CHAIN(w)     TO_HP_BITS ( 8, 11, w)

#define CPI_READ_FLAGS      (CPI_RIGHT_BYTE | CPI_NO_INCREMENT | CPI_EX_ADDR)
#define CPI_BURST_FLAGS     (CPI_RIGHT_BYTE | CPI_TERM_LF | CPI_DIRECTION | CPI_NO_INCREMENT | CPI_EX_ADDR)
#define CPI_RECORD_FLAGS    (CPI_RIGHT_BYTE | CPI_TERM_LF | CPI_DIRECTION | CPI_NO_INCREMENT | CPI_EX_ADDR)


/* Write

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   0   1   0   0 |  Data Chain   |   Modifier    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                  Byte Count / Residue Count                   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   -   -   - |         Burst Length          |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | B | R | E | D | N | U | -   - |    Extended Memory Address    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Memory Address / Residue Address                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CPI_NO_EOI          HP_BIT (2)          /* (E) Do not tag last byte of burst with EOI */


/* Device Specified Jump

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   0   1   0   1 |     Maximum Response (N)      |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   -   -   - |         Returned Byte         |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Relative Displacement for Byte 0                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Relative Displacement for Byte 1                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                              ,,,                              |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Relative Displacement for Byte N                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CPI_RESPONSE(w)     HP_BITS_TO (8, 15, w)


/* Clear

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   0   0   1 |         Control Byte          |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   - |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CPI_CONTROL(w)      HP_BITS_TO (8, 15, w)


/* Read-Modify-Write

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   0   1   0 | -   -   -   - |   Register    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   -   -   -   -   -   -   -   - | S |  Bit Number   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CPI_SET_BIT         HP_BIT (11)         /* (S) set bit */

#define CPI_REGISTER(w)     HP_BITS_TO (12, 15, w)
#define CPI_BIT_NUMBER(w)   HP_BITS_TO (12, 15, w)


/* Read Register

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   0   1   1 | -   -   -   - |   Register    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                         Returned Word                         |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Write Register

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   1   0   0 | -   -   -   - |   Register    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                         Supplied Word                         |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   The Read Register and Write Register accessors are the same as the
   Read-Modify-Write accessors, except that the CPI_SET_BIT and CPI_BIT_NUMBER
   are not defined.
*/


/* Command HP-IB

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   1   0   1 | -   -   -   -  -  |   Count   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |           Command 1           |           Command 2           |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |           Command 3           |           Command 4           |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |           Command 5           |           Command 6           |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |           Command 7           |           Command 8           |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/

#define CPI_CMD_COUNT(w)    HP_BITS_TO (13, 15, w)


/* Execute DMA

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   0   1   1   1   0 | -   -   -   -   -   -   -   - |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                  Byte Count / Residue Count                   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |   Termination Displacement    |         Burst Length          |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | B | R | T | D | N | U | -   - |    Extended Memory Address    |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |               Memory Address / Residue Address                |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   The Execute DMA accessors are the same as the Read accessors, except that the
   CPI_DATA_CHAIN and CPI_MODIFIER are not defined.
*/


/* CRC Compare

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0   0   0   1   0   0   0   1 | H | I | -   -   -   - | CPVA  |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | -   -   -   - |                Interrupt Code                 |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   The other CRC Compare accessors are the same as the Interrupt accessors.
*/

#define CPI_IRQ_MATCH       HP_BIT (9)          /* (I) Interrupt on CRC match */


/* Cold load channel programs.

   These programs are used to cold-load from GIC-connected devices on HP-IB
   systems and Series III systems with a Starfish interface.


   Implementation notes:

    1. The CS/80 load program microcode uses burst-mode writes, with the burst
       length equal to the record length.  The rationale for using this instead
       of record-mode writes is not obvious, but the simulation follow the
       microcode.
*/

typedef enum {                                  /* cold load program states */
    CL_Idle,                                    /*   the program is idle */
    CL_Identify,                                /*   executing the Identify program */
    CL_First_Load,                              /*   executing the first load program */
    CL_Second_Load,                             /*   executing the second load program */
    CL_Timeout                                  /*   the program has timed out */
    } COLD_LOAD_STATE;


static const MEMORY_WORD identify [] = {        /* Amigo Identify program */
    0003000, 0000000,                           /*   Identify */
    0000600, 0000000                            /*   Interrupt/Halt */
    };

static const MEMORY_WORD cs80_load [] = {                   /* CS/80 cold load program */
    0003000, 0000000,                                       /*   Identify */
    0001000, 0000000,                                       /*   Wait */
    0002403, 0000000, 0000000, 0000074, 0177766, 0000074,   /*   DSJ */
    0002005, 0000011, 0000011, 0100000, 0001477,            /*   Write (Set Status Mask) */
    0001000, 0000000,                                       /*   Wait */
    0002402, 0000000, 0000000, 0000060, 0177752,            /*   DSJ */
    0002005, 0000015, 0000015, 0140000, 0001503,            /*   Write (Set Address, Set Length, Locate and Read) */
    0001000, 0000000,                                       /*   Wait */
    0001416, 0000400, 0000000, 0000000, 0007100,            /*   Read */
    0001000, 0000000,                                       /*   Wait */
    0002403, 0000000, 0000000, 0000034, 0000034, 0000034,   /*   DSJ */
    0000600, 0000000,                                       /*   Interrupt/Halt */
    0037000, 0000000, 0000000, 0000010, 0072021,            /*   001477-001503 data words */
    0000000, 0000000, 0000002, 0014000, 0000001, 0000000,   /*   001504-001511 data words */
    0000015                                                 /*   001512 data word */
    };

static const MEMORY_WORD cs80_status [] = {             /* CS/80 read status program */
    0002005, 0000001, 0000000, 0140000, 0001512,        /*   Write (Request Status) */
    0001000, 0000000,                                   /*   Wait */
    0001416, 0000024, 0000000, 0002000, 0001547,        /*   Read */
    0000600, 0000003                                    /*   Interrupt/Halt */
    };

static const MEMORY_WORD mac_load [] = {                /* MAC cold load program */
    0001000, 0000000,                                   /*   Wait */
    0002010, 0000002, 0000000, 0000000, 0001476,        /*   Write (Set Filemask) */
    0001000, 0000000,                                   /*   Wait */
    0002010, 0000002, 0000000, 0000000, 0001503,        /*   Write (Read Status) */
    0001410, 0000004, 0000000, 0000000, 0001504,        /*   Read (4 status bytes) */
    0001000, 0000000,                                   /*   Wait */
    0002010, 0000006, 0000000, 0000000, 0001477,        /*   Write (Seek) */
    0001000, 0000000,                                   /*   Wait */
    0002010, 0000002, 0000000, 0000000, 0001502,        /*   Write (Read Data) */
    0001400, 0000400, 0000000, 0000000, 0007100,        /*   Read (256 bytes to address %7100) */
    0001000, 0000000,                                   /*   Wait */
    0000600, 0000000,                                   /*   Interrupt/Halt */
    0177777,                                            /*   (invalid) */
    0007405,                                            /*   (filemask command) */
    0001000, 0000000, 0000002,                          /*   (seek command) */
    0002400,                                            /*   (read command) */
    0001400,                                            /*   (status command) */
    0000000, 0000000                                    /*   (status return buffer) */
    };

static const MEMORY_WORD tape_load [] = {               /* Amigo tape cold load program */
    0002001, 0000001, 0000000, 0042000, 0001424,        /*   Write (Select Unit 0) */
    0001000, 0000000,                                   /*   Wait */
    0002400, 0000000, 0000000,                          /*   DSJ */
    0002001, 0000001, 0000000, 0042000, 0001501,        /*   Write (Read Record( */
    0001000, 0000000,                                   /*   Wait */
    0002400, 0000000, 0000000,                          /*   DSJ */
    0001400, 0000400, 0002100, 0100000, 0007100,        /*   Read (256 bytes to address %7100) */
    0000000, 0000002,                                   /*   Relative Jump *+2 (count done) */
    0000000, 0177762,                                   /*   Relative Jump *-14 (burst done) */
    0002007, 0000001, 0000000, 0042000, 0001502,        /*   Write (End) */
    0001402, 0000002, 0000000, 0002000, 0001503,        /*   Read (2 byte count) */
    0001000, 0000000,                                   /*   Wait */
    0002400, 0000000, 0000000,                          /*   DSJ */
    0000600, 0000000,                                   /*   Interrupt/Halt */
    0000010,                                            /*   (Read Record command) */
    0000023                                             /*   (End command) */
    };


/* Global CPP state */

uint32 cpp_request = 0;                         /* delay until a pending channel processor request is serviced */


/* Local CPP state */

static const uint32 controller = 30u;           /* bus address of the controller */

static t_bool  interrupt_enable    = FALSE;     /* TRUE if the CPP is enabled to interrupt the CPU */
static HP_WORD interrupt_mask      = 0;         /* interrupt mask word */
static uint32  execution_limit_set = 0;         /* the set of channels that have reached the execution limit */
static uint8   restart_set [CHAN_MAX + 1];      /* the set of channel devices that must be restarted */


/* Local CPP routines */

static void    process_imba      (void);
static HP_WORD setup_channel     (uint32 channel);
static void    process_channel   (uint32 channel);
static void    cleanup_program   (uint32 channel, uint32 device, uint32 drt);
static void    abort_program     (uint32 channel, uint32 device, uint32 drt, uint32 code);
static void    halt_program      (uint32 channel, uint32 device, uint32 drt);
static void    suspend_program   (uint32 channel, uint32 device, uint32 drt, HP_WORD status);
static void    execute_program   (uint32 channel, uint32 device, uint32 drt, HP_WORD drt0);
static void    initiate_dma      (uint32 channel, uint32 device, uint32 drt,
                                  uint32 drt0, HP_WORD operand, HP_WORD flags);
static void    complete_dma      (uint32 channel, uint32 device, uint32 drt);
static t_bool  update_chain      (uint32 drt, HP_WORD drt0, HP_WORD instruction);
static void    clear_fifos       (uint32 channel);
static void    suspend_for_data  (uint32 channel, uint32 device, uint32 drt);

static t_stat  activate_unit     (UNIT *uptr, int32 delay);
static void    trace_program     (HP_WORD starting_address);
static void    trace_instruction (uint32 channel, HP_WORD instruction, HP_WORD operand, HP_WORD drt0);


/* CPP local SCP support routines */

static t_stat imba_service    (UNIT *uptr);
static t_stat timeout_service (UNIT *uptr);
static t_stat cpp_reset       (DEVICE *dptr);


/* CPP SCP data structures */


/* Unit list */

static UNIT cpp_unit [] = {
    { UDATA (&imba_service,    UNIT_DIS, 0) },  /* the IMBA completion handler */
    { UDATA (&timeout_service, UNIT_DIS, 0) }   /* the cold load timeout handler */
    };

static const char *unit_names [] = { "Completion", "Timeout" };


/* Register list */

static REG cpp_reg [] = {
/*    Macro   Name      Location              Radix  Width  Offset      Depth     */
/*    ------  --------  --------------------  -----  -----  ------  ------------- */
    { FLDATA (IEN,      interrupt_enable,                     0)                  },
    { YRDATA (LIMIT,    execution_limit_set,          16)                         },
    { BRDATA (RESTART,  restart_set,            2,     8,           CHAN_MAX + 1) },

    { NULL }
    };


/* Debugging trace list */

static DEBTAB cpp_deb [] = {
    { "CMD",   TRACE_CMD   },                   /* controller commands */
    { "OPND",  TRACE_OPND  },                   /* SIOP channel program instructions */
    { "SERV",  TRACE_SERV  },                   /* controller unit service scheduling calls */
    { "DATA",  TRACE_DATA  },                   /* CPP memory data accesses (used by mem_read, mem_write) */
    { NULL,    0           }
    };


/* Device descriptor */

DEVICE cpp_dev = {
    "CPP",                                      /* device name */
    cpp_unit,                                   /* unit array */
    cpp_reg,                                    /* register array */
    NULL,                                       /* modifier array */
    2,                                          /* number of units */
    8,                                          /* address radix */
    PA_WIDTH,                                   /* address width */
    1,                                          /* address increment */
    8,                                          /* data radix */
    DV_WIDTH,                                   /* data width */
    NULL,                                       /* examine routine */
    NULL,                                       /* deposit routine */
    &cpp_reset,                                 /* reset routine */
    NULL,                                       /* boot routine */
    NULL,                                       /* attach routine */
    NULL,                                       /* detach routine */
    NULL,                                       /* device information block pointer */
    DEV_DEBUG,                                  /* device flags */
    0,                                          /* debug control flags */
    cpp_deb,                                    /* debug flag name array */
    NULL,                                       /* memory size change routine */
    NULL                                        /* logical device name */
    };



/* CPP global routines */


/* Cold load a program from an HP-IB device.

   This routine is called from the CPU halt-mode interrupt handler when the
   front panel LOAD switch is pressed and the lower byte of the switch register
   contains the device number of the Intermodule Bus Adapter.  The upper byte of
   the switch register contains the number of the cold load device and is passed
   to this routine.

   The cold load microcode follows this series of steps:

     - Identify the cold load device

     - Copy a device-specific channel program to memory starting at %1423.

     - Run the channel program, which reads a second cold load program from disc
       sector 2 into memory starting at %7100.

     - Verify the checksum of the channel program.

     - Run the second channel program, which reads a third cold load program
       from the disc into memory starting at %2000.  The second program ends
       with a JUMP to the start of the third program.

     - When the third program ends, trap to the cold load handler.

   The microcode runs these steps in sequence, with local wait loops for the
   channel programs to finish.  In simulation, the event queue must be run in
   order for disc, tape, etc. event scheduling to work.  Therefore, loading is
   performed in several stages, and the routine returns to the caller in between
   stages to allow event services to be handled.  Consequently, a state machine
   is used to track the loading steps, and the routine returns TRUE when loading
   is complete and FALSE for the intermediate steps.

   There are three places in the cold load sequence that must wait:

     - For the Identify bytes to arrive.

     - For the Interrupt/Halt when the first channel program completes.

     - For the Interrupt/Halt when the third channel program completes.

   After the last of these, and presuming success, the routine returns TRUE,
   causing the cold load trap to be taken to start MPE.

   Initial entry has "cpu_micro_state" set to "halted". This is changed to
   "waiting" upon return, so subsequent entries see this value, which causes the
   routine to use the next state.


   Implementation notes:

    1. The microcode sends the initial Identify sequence directly and then
       watches the FIFO to determine when the bytes have been returned.  In
       simulation, a channel program is run that does an Identify and then an
       Interrupt/Halt, so that all three wait instances may be handled
       identically.

    2. The microcode also sets up timeouts for the loops to ensure that it
       doesn't hang in case of a non-responding device.  In simulation, this is
       handled by scheduling a timeout event.  The service routine cannot do a
       MICRO_ABORT directly, as that would disrupt the "sim_process_event"
       caller.  Instead, it advances to the timeout state and does an IMBA
       interrupt.  When the interrupt is handled, we will be called to issue the
       trap.
*/

t_bool cpp_cold_load (uint32 cold_load_device)
{
const uint32 channel = IO_CHANNEL (cold_load_device);
const uint32 device  = IO_DEVICE  (cold_load_device);
const uint32 drt     = TO_DRT     (cold_load_device);
IO_RESULT result;
IMB_DATA  response;
HP_WORD   id, cpva0, value, accumulator, checksum;
uint32    address;

if (cpu_micro_state == halted) {                        /* if the load has not started */
    Loader_State = CL_Idle;                             /*   then set the initial state */
    Loader_Device = cold_load_device;                   /*     and the device number in case of a timeout */
    }

switch (Loader_State) {                                 /* dispatch the current loader state */

    case CL_Idle:                                       /* Idle */
        result = cpp_io_op (5, 0, 0);                   /* issue an I/O Clear to all channels */

        response = imb_io_read (channel, Reg_1);        /* read the PHI status register */

        if (not (response & R1_SYSCNTLR))               /* if the channel is not the system controller */
            MICRO_ABORT (trap_SysHalt_Not_SysCntlr);    /*   then the failure is fatal */

        else {                                              /* otherwise */
            result = cpp_io_op (6, cold_load_device, 0);    /* issue an INIT to the cold load channel */

            if (result.condition == STATUS_CCG)         /* if the PHI did not become CIC */
                MICRO_ABORT (trap_SysHalt_Not_CIC);     /*   then the error is fatal */
            else                                        /* otherwise */
                imb_io_write (channel, Reg_0, PHI_DCL); /*   issue a Universal Device Clear */
            }

        mem_copy_loader (001423, identify, sizeof identify);    /* install the Identify channel program */

        mem_write (&cpp_dev, absolute, drt + 0, 001423);        /* set the cold load program pointer */
        mem_write (&cpp_dev, absolute, drt + 2, 001427);        /*   and the CPVA pointer */
        mem_write (&cpp_dev, absolute, drt + 3, CPS_STARTING);  /*     and the channel starting state */

        interrupt_enable = TRUE;                        /* enable channel interrupts */

        imb_io_cycle (SMSK, 0, HP_BIT (channel));               /* enable the channel interrupt mask */
        imb_io_cycle (SIOP, channel, device | SIOP_SET_STATUS); /*   and start the program */

        if (TRACING (cpp_dev, TRACE_OPND))              /* trace the channel program if enabled */
            trace_program (001423);

        activate_unit (&Timeout_Unit, AMIGO_ID_DELAY);  /* start the watchdog timer */
        Loader_State = CL_Identify;                     /*   and set the next state */
        break;


    case CL_Identify:                                   /* Identify */
        sim_cancel (&Timeout_Unit);                     /* cancel the timer */
        Timeout_Unit.wait = -1;                         /*   and reset the activation time */

        imb_io_write (channel, Reg_C, device);          /* clear the interrupt */

        mem_read (&cpp_dev, absolute, 001424, &id);     /* get the device identification code */

        if (UPPER_BYTE (id) == 2) {                                     /* if the ID is CS/80 */
            mem_copy_loader (001423, cs80_load,   sizeof cs80_load);    /*   then install the load program */
            mem_copy_loader (001531, cs80_status, sizeof cs80_status);  /*      and the error status program */
            }

        else if (UPPER_BYTE (id) == 0)                              /* otherwise if it is MAC disc */
            mem_copy_loader (001423, mac_load, sizeof mac_load);    /*   then install the load program */

        else if (UPPER_BYTE (id) == 1)                              /* otherwise if it is Amigo tape */
            mem_copy_loader (001423, tape_load, sizeof tape_load);  /*   then install the load program */

        else                                            /* otherwise the ID is unrecognized */
            MICRO_ABORT (trap_SysHalt_IO_Timeout);      /*   and the error is fatal */

        mem_write (&cpp_dev, absolute, drt + 0, 001423);        /* reset the cold load program pointer */
        mem_write (&cpp_dev, absolute, drt + 3, CPS_STARTING);  /*   and the channel starting state */

        interrupt_enable = TRUE;                        /* enable channel interrupts */

        imb_io_cycle (SIOP, channel, device | SIOP_SET_STATUS); /* start the program */

        if (TRACING (cpp_dev, TRACE_OPND))              /* trace the channel program if enabled */
            trace_program (001423);

        activate_unit (&Timeout_Unit, COLD_LOAD_DELAY); /* start the watchdog timer */
        Loader_State = CL_First_Load;                   /*   and set the next state */
        break;


    case CL_First_Load:                                 /* First Program Load */
        sim_cancel (&Timeout_Unit);                     /* cancel the timer */
        Timeout_Unit.wait = -1;                         /*   and reset the activation time */

        mem_read (&cpp_dev, absolute, 007100, &checksum);   /* get the block checksum */

        accumulator = 0123456;                          /* seed the initial accumulator value */

        for (address = 07101; address < 07300; address++) { /* checksum the channel program */
            mem_read (&cpp_dev, absolute, address, &value); /*   just loaded into memory */
            accumulator += value;                           /*     before executing it */
            }

        if (LOWER_WORD (accumulator) != checksum)       /* if the checksum is incorrect */
            MICRO_ABORT (trap_SysHalt_CP_Checksum);     /*   then report a fatal error */

        imb_io_write (channel, Reg_C, device);          /* clear the interrupt */

        mem_write (&cpp_dev, absolute, drt + 0, 007101);        /* set the new program pointer */
        mem_write (&cpp_dev, absolute, drt + 3, CPS_STARTING);  /*   and the channel starting state */

        interrupt_enable = TRUE;                        /* enable channel interrupts */

        imb_io_cycle (SIOP, channel, device | SIOP_SET_STATUS); /* start the program */

        if (TRACING (cpp_dev, TRACE_OPND))              /* trace the channel program if enabled */
            trace_program (007101);

        activate_unit (&Timeout_Unit, COLD_LOAD_DELAY); /* start the watchdog timer */
        Loader_State = CL_Second_Load;                  /*   and set the next state */
        break;


    case CL_Second_Load:                                /* Second Program Load */
        sim_cancel (&Timeout_Unit);                     /* cancel the timer */
        Timeout_Unit.wait = -1;                         /*   and reset the activation time */

        imb_io_write (channel, Reg_C, device);          /* clear the interrupt */

        interrupt_enable = TRUE;                        /* enable channel interrupts */

        mem_read (&cpp_dev, absolute, 001427, &cpva0);  /* get the CPVA result */

        if (cpva0 != CPA_INTERRUPT)                     /* if an abort was logged */
            MICRO_ABORT (trap_SysHalt_CP_Abort);        /*   then the error is fatal */

        return TRUE;                                    /* tell the CPU that loading is complete */

    case CL_Timeout:                                    /* Cold Load Program Timeout */
        MICRO_ABORT (trap_SysHalt_IO_Timeout);          /*   is a fatal error */
        break;
    }

return FALSE;                                           /* indicate that loading is incomplete */
}


/* Channel Program Processor.

   This routine is called from the CPU instruction execution loop to service a
   pending channel or interrupt request, and the CPP entry delay has expired.
   It executes one or more channel cycles for the associated device interface
   before exiting.  IMB interrupt requests are reflected back to the Series III
   IOP via the Intermodule Bus Adapter.  Channel requests typically involve
   initiating a channel program or executing one or more program instructions.

   The CPP is entered indirectly to start or halt a channel program.  The SIOP
   and HIOP instructions set a bit in a channel register that causes the channel
   to assert CSRQ.  This causes CPP entry when the next machine instruction is
   fetched.

   Once a channel program is started, it typically executes until it waits for a
   device response.  However, execution time is limited and, e.g., in the case
   of a program that loops without waiting, the processor will run until the
   execution limit is reached.  A program that exits from hitting this limit
   will be resumed after a short delay to allow the CPU some time to execute.

   On entry, the "imb_interrupt_request_set" and "imb_channel_request_set"
   global variables will have bits set corresponding to those channels
   requesting interrupts or channel service, respectively,  Each call of this
   routine will service one request, with interrupt requests having priority
   over channel service requests, and both having priority over execution limit
   resumption requests.  Within each request set, lower channel numbers have
   priority over higher numbers.

   Once all pending requests are handled, the routine idles until a new request
   is received.


   Implementation notes:

    1. The "ticks_elapsed" parameter represents the number of CPU event ticks
       that have elapsed since the last call.  It is currently unused.

    2. Channel number 0 is undefined, and channel 1 is reserved for the ADCC
       containing the system console (for HP-IB systems) or for the IMBA (for
       Starfish-equipped Series III systems).

    3. The imb_io_write call to channel register C to clear an interrupt request
       will also clear the corresponding bit in the imb_interrupt_request_set,
       so it is not necessary to do it explicitly here.  A similar action occurs
       with the imb_io_cycle call to send a SPOL1 command to the channel
       requesting service.
*/

void cpp_service (uint32 ticks_elapsed)
{
IMB_DATA poll;
HP_WORD  devno, drt0;
uint32   channel, device, chan_bit, dev_bit, drt;

tprintf (cpp_dev, TRACE_SERV, "Channel processor running\n");

if (imb_interrupt_request_set && interrupt_enable) {    /* if an interrupt request is pending and enabled */
    chan_bit = IOPRIORITY (imb_interrupt_request_set);  /*   then get the highest priority request */
    channel = BIT_NUMBER (chan_bit);                    /*     and the corresponding channel number */

    poll = imb_io_cycle (IPOLL, channel, 0);            /* send an interrupt poll to the requesting channel */

    if (poll != 0) {                                    /* if the request is still active */
        tprintf (cpp_dev, TRACE_CMD,
                 "Channel processor interrupted by channel %u\n", channel);

        poll = imb_io_cycle (OBII, channel, 0);         /* send an interrupt info request to the channel */

        if (not (poll & OBII_INVALID)) {                /* if the interrupt is still valid */
            devno = OBII_CHANDEV (poll);                /*   then mask to just the channel and device number */

            imb_io_write (IMBA_CHANNEL, 0, devno);      /* tell the IMBA to interrupt */

            imb_io_write (channel, Reg_C, RC_DEVICE (poll));    /* clear the device interrupt */

            interrupt_enable = FALSE;                   /* disable interrupts */
            }
        }
    }

else if (imb_channel_request_set) {                     /* otherwise if a channel request is pending */
    chan_bit = IOPRIORITY (imb_channel_request_set);    /*   then get the highest priority request */
    channel = BIT_NUMBER (chan_bit);                    /*     and the corresponding channel number */

    poll = imb_io_cycle (SPOL1, channel, 0);            /* send a service poll to the requesting channel */

    if (poll != 0) {                                    /* if the request is still active */
        tprintf (cpp_dev, TRACE_CMD,
                 "Channel processor servicing channel %u\n", channel);

        if (channel == 1)                               /*   then if the IMBA is requesting service */
            process_imba ();                            /*     then process its request */
        else                                            /*   otherwise a channel is requesting service */
            process_channel (channel);                  /*     so process the indicated channel */
        }
    }

else if (execution_limit_set) {                         /* if an execution limit restart is pending */
    chan_bit = IOPRIORITY (execution_limit_set);        /*   then get the highest priority channel request */
    channel = BIT_NUMBER (chan_bit);                    /*     and the corresponding channel number */

    dev_bit = IOPRIORITY (restart_set [channel]);       /* get the highest priority device request */
    device  = BIT_NUMBER (dev_bit);                     /*   and the corresponding device number */

    restart_set [channel] ^= dev_bit;                   /* clear the device limit bit */

    if (restart_set [channel] == 0)                     /* if all device limit bits are clear */
        execution_limit_set ^= chan_bit;                /*   then clear the channel limit bit */

    drt = TO_DRT (TO_DEVNO (channel, device));          /* get the associated DRT address */

    mem_read (&cpp_dev, absolute, drt + 0, &drt0);      /* read the channel program pointer */
    execute_program (channel, device, drt, drt0);       /*   and resume the channel program */
    }

if (imb_channel_request_set                             /* if a channel request is still pending */
  || imb_interrupt_request_set && interrupt_enable) {   /*   or a qualified interrupt request */
    cpp_request = CPP_DELAY;                            /*     then reschedule the service */

    tprintf (cpp_dev, TRACE_SERV, "Channel processor rescheduled\n");
    }

else if (execution_limit_set) {                         /* otherwise if the execution limit was reached */
    cpp_request = RESTART_DELAY;                        /*    then reschedule the service */

    tprintf (cpp_dev, TRACE_SERV, "Channel processor suspended\n");
    }

else                                                    /* otherwise idle the CPP */
    tprintf (cpp_dev, TRACE_SERV, "Channel processor idled\n");

return;
}


/* Execute a channel I/O operation.

   This routine executes the set of HP-IB I/O instructions that are common to
   the Series III Starfish and the Series 4x/5x systems.  On entry, the opcode
   parameter contains a value indicating the operation to perform.  The Starfish
   opcode is the value residing in mailbox word 0 when an SIO operation is
   issued to the IMBA.  The Series 4x/5x opcode is the second word of a two-word
   CPU instruction where the first word is the octal value 020302.  The opnd_1
   and opnd_2 parameters contain the operand values corresponding to the data
   words residing on the stack in registers RA and RB, respectively, for the
   equivalent machine instruction.  The routine returns a structure containing
   the word obtained from a read operation, if present, and the condition code
   indicating the success or failure of the operation.

   The operations implemented are:

     Inst  Opcode  CCL  CCE  CCG  Action
     ----  ------  ---  ---  ---  ----------------------
     SIOP  000000   *    *    *   Start I/O Program
     HIOP  000001   -    *    *   Halt I/O Program
     RIOC  000002   *    *    -   Read I/O Channel
     WIOC  000003   *    *    -   Write I/O Channel
     ROCL  000004        *        Roll Call
     IOCL  000005   -    -    -   I/O Clear
     INIT  000006   -    *    *   Initialize I/O Channel

   The returned condition code is CCE if the operation succeeded.  Otherwise,
   the condition code responses are:

     Inst   CCL Response    CCG Response
     ----   -------------   ---------------
     SIOP   power failure   already running
     HIOP   --              will halt later
     RIOC   power failure   --
     WIOC   power failure   --
     ROCL   --              --
     IOCL   --              --
     INIT   --              PHI is offline


   Implementation notes:

    1. Opcode 4 is the Roll Call instruction for the HP-IB machines and the Set
       Enable/Disable Interrupt instruction for the Starfish.  We implement the
       former here and the latter in the Starfish-specific executor.

    2. The invalid condition code, STATUS_CCI, is returned if the operation
       requested is not valid for the current CPU model.
*/

IO_RESULT cpp_io_op (uint32 opcode, HP_WORD opnd_1, HP_WORD opnd_2)
{
IO_RESULT    result;
IMB_OUTBOUND response;
IMB_DATA     device;
HP_WORD      drt3;
uint32       channel, drt, index;

switch (opcode) {                                       /* dispatch the I/O operation */

    case 000:                                           /* Start I/O Program */
        drt = TO_DRT (opnd_2);                          /* set the DRT address from the channel and device */
        mem_read (&cpp_dev, absolute, drt + 3, &drt3);  /*   and get the channel program status word */

        if (drt3 & CPS_POWERFAIL)                       /* if power fail recovery is in process */
            result.condition = STATUS_CCL;              /*   then set CCL */

        else if ((drt3 & CPS_EXEC_MASK) == CPS_STOPPED              /* otherwise if the channel program is halted */
          || (drt3 & CPS_EXEC_MASK) == CPS_STOPPING                 /*   or is halting */
          && (drt3 & CPS_PROGWAIT) == CPS_PROGWAIT) {               /*     in the waiting state */
            mem_write (&cpp_dev, absolute, drt + 0, opnd_1);        /*       then set the program pointer */
            mem_write (&cpp_dev, absolute, drt + 3, CPS_STARTING);  /*         and set the run-pending state */

            channel = IO_CHANNEL (opnd_2);              /* get the channel number */
            device  = IO_DEVICE  (opnd_2);              /*   and the device number */

            imb_io_cycle (SIOP, channel,                /* send an SIOP command to the channel */
                          device | SIOP_SET_STATUS);

            result.condition = STATUS_CCE;              /* set CCE for a successful start */

            if (TRACING (cpp_dev, TRACE_OPND))          /* trace the channel program if enabled */
                trace_program (opnd_1);
            }

        else                                            /* otherwise a channel program is running */
            result.condition = STATUS_CCG;              /*   so report that we can't start the new one */
        break;


    case 001:                                           /* Halt I/O Program */
        drt = TO_DRT (opnd_1);                          /* set the DRT address from the channel and device */
        mem_read (&cpp_dev, absolute, drt + 3, &drt3);  /*   and get the channel program status word */

        if ((drt3 & CPS_EXEC_MASK) == CPS_STOPPED)      /* if the channel program is not running */
            result.condition = STATUS_CCE;              /*   then set CCE */

        else {                                          /* otherwise */
            if (drt3 & CPS_RUNNING) {                   /*   if the program is running */
                if (drt3 & CPS_PENDING)                 /*     then if the run is still pending */
                    drt3 = drt3 | CPS_PROGWAIT;         /*       then set the waiting bit */

                drt3 = drt3 & ~CPS_STATE_MASK | CPS_STOPPING;   /* change the state to stopping */

                mem_write (&cpp_dev, absolute,          /* rewrite the program status word */
                           drt + 3, drt3);

                channel = IO_CHANNEL (opnd_1);          /* get the channel number */
                device  = IO_DEVICE (opnd_1);           /*   and the device number */

                imb_io_cycle (HIOP, channel,            /* send an HIOP command to the channel */
                              device | HIOP_SET_STATUS);
                }

            if (drt3 & CPS_PROGWAIT)                    /* if the program is in the wait state */
                result.condition = STATUS_CCE;          /*   then set CCE */
            else                                        /* otherwise the program is running */
                result.condition = STATUS_CCG;          /*   so set CCG to expect an interrupt */
            }
        break;


    case 002:                                           /* Read I/O Channel */
        if (opnd_1 & IMB_GLOBAL)                        /* if the read command is global */
            opnd_1 &= IMB_GLOBAL_MASK;                  /*   then mask off the channel number */

        else {                                              /* otherwise */
            drt = TO_DRT (opnd_1);                          /*   set the DRT address from the channel and device */
            mem_read (&cpp_dev, absolute, drt + 3, &drt3);  /*     and get the channel program status word */

            if (drt3 & CPS_POWERFAIL) {                 /* if power fail recovery is in process */
                result.condition = STATUS_CCL;          /*   then set CCL */
                break;                                  /*     and skip the read */
                }
            }

        response = imb_cycle (IMB_IO_Read, opnd_1, 0);  /* send the read command to the channel */
        result.data = response.data;                    /*   and return the result */

        if (response.signals & DNV)                     /* if the data is not valid */
            result.condition = STATUS_CCL;              /*   then set CCL */
        else                                            /* otherwise */
            result.condition = STATUS_CCE;              /*   set CCE for success */
        break;


    case 003:                                           /* Write I/O Channel */
        if (opnd_2 & IMB_GLOBAL)                        /* if the write command is global */
            opnd_2 &= IMB_GLOBAL_MASK;                  /*   then mask off the channel number */

        else {                                              /* otherwise */
            drt = TO_DRT (opnd_2);                          /*   set the DRT address from the channel and device */
            mem_read (&cpp_dev, absolute, drt + 3, &drt3);  /*     and get the channel program status word */

            if (drt3 & CPS_POWERFAIL) {                 /* if power fail recovery is in process */
                result.condition = STATUS_CCL;          /*   then set CCL */
                break;                                  /*     and skip the write */
                }
            }

        imb_cycle (IMB_IO_Write, opnd_2, opnd_1);       /* send the write command to the channel */
        result.condition = STATUS_CCE;                  /*   and set CCE for success */
        break;


    case 004:                                           /* Channel Roll Call */
        result.condition = STATUS_CCI;                  /* set the invalid condition */
        break;                                          /*   as this instruction is not implemented */


    case 005:                                           /* I/O Clear */
        imb_io_cycle (IOCL, 0, 0);                      /* send an IOCL command to IMB */
        result.condition = STATUS_CCE;                  /*   and set CCE for success */
        break;


    case 006:                                           /* Initialize I/O Channels */
        channel = IO_CHANNEL (opnd_1);                  /* get the channel number */
        imb_io_cycle (INIT, channel, 0);                /*   and send an INIT command to the channel */

        drt = TO_DRT (opnd_1 & IO_CHANNEL_MASK);        /* set the DRT address from the channel number */

        for (index = 0; index <= BUS_MAX; index++, drt += 4)    /* clear DRT word 3 for each */
            mem_write (&cpp_dev, absolute, drt + 3, 0);         /*   bus device on the channel */

        result.condition = setup_channel (channel);     /* set up the channel */
        break;


    default:                                            /* all other opcodes */
        result.condition = STATUS_CCI;                  /*   set the invalid condition */
        break;                                          /*     as they are not implemented */
    }

return result;                                          /* return the execution result */
}



/* CPP local routines */


/* Process an IMBA command.

   The Series III initiates Starfish commands by storing codes in the mailbox
   locations in memory and executing an SIO I/O order to the IMBA.  The latter
   causes CSRQ to assert, which runs the Channel Program Processor.  The CPP
   sees that the request is coming from the IMBA, which is permanently assigned
   to channel 1, and it calls this routine to examine the mailbox and perform
   the indicated action.

   Communication between the Series III and the Starfish is handled via a
   mailbox consisting of eight absolute memory locations occupying addresses
   770-777 octal in bank 0.  These correspond to DRT entries 126 and 127, which
   are otherwise unused.  The mailbox locations are assigned the following
   meanings:

     Box  Location  Use
     ---  --------  ----------------------------------------------------
      0     770     Instruction Opcode (0-7)
      1     771     DRT number or Channel Number or Enable/Disable (SED)
                      or IMB Command (RIOC/WIOC)
      2     772     Data Read (RIOC) or Written (WIOC)
      3     773     Channel Program Pointer
      4     774     Operation Status
      5     775     (not used)
      6     776     (not used)
      7     777     (not used)

   The instruction opcodes for mailbox location 0 are:

     Inst  Opcode  Action
     ----  ------  -----------------------------
     SIOP  000000  Start I/O Program
     HIOP  000001  Halt I/O Program
     RIOC  000002  Read I/O Channel
     WIOC  000003  Write I/O Channel
     SED2  000004  Enable/Disable CPP interrupts
     IOCL  000005  I/O Clear
     INIT  000006  Initialize I/O Channel
     SLFT  000007  Initiate self-test

   The SED2 and SLFT instructions are specific to the Starfish; the opcodes
   correspond to the ROCL (Roll Call) and MCS (Memory Control/Status)
   instructions for the Series 4x/5x.  The others are standard 4x/5x
   instructions listed in the Machine Instruction Set.

   Mailbox 4 bits have this significance after an SIO instruction:

       0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | F | P | T | M | O | D | H | W | S | C | V | -   -   -   - | E |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     F = Operation is complete (idle)
     P = IMB parity error
     T = Invalid timer interrupt
     M = Non-responding module timeout
     O = Invalid mailbox opcode
     D = SIO disabled flag during SIOP, RIOC, or WIOC
     H = SIOP failed because previous channel program is not halted
     W = SIOP or HIOP failure - halt pending but not in WAIT
     S = INIT failed - unable to bring system controller on-line
     C = INIT failed - GIC not system controller
     V = Data not valid
     E = Any error (logical OR of the other error bits)

   After executing the SIO program, the Series III waits for bit 0 to set.  This
   indicates that the CPP has recognized the CSRQ and has begun execution of the
   HP-IB machine instruction.  If bit 15 is set, then the other bits indicate
   the cause of the error.


   Implementation notes:

    1. For the Starfish, IOCL must set up the PHI to put it back online as the
       controller.  Otherwise, the GIC diagnostic fails.

    2. The GIC schematic shows that IOCL, INIT, and power-on all reset the
       interrupt mask flip-flop.  However, for the Starfish, IOCL must do a SMSK
       to reenable the mask.  Otherwise, cold loading MPE succeeds, but after
       entering the INITIAL dialog, MPE hangs with the GIC mask flip-flop clear.

    3. Although the CE handbook states that the Starfish recognizes instruction
       opcodes 0-7, the Starfish GIC diagnostic issues an instruction with
       opcode 12 octal.  If this instruction fails with return status bit 4
       (Invalid Mailbox Opcode) set, the diagnostic reports errors.  Therefore,
       we accept that opcode as valid, although its function is unknown.

    4, Bits 11-14 of mailbox word 4 are not assigned but must be zero to pass
       the GIC diagnostic.
*/

static void process_imba (void)
{
IO_RESULT result;
IMB_DATA  poll;
HP_WORD   opcode, mask, opA, opB;
uint32    channel;
int32     delay = BUS_DELAY;

mem_read (&cpp_dev, absolute, MBX0, &opcode);           /* read the instruction opcode */

tprintf (cpp_dev, TRACE_CMD, "Channel processor executing %s%.0o\n",
         (opcode <= 7 ? starfish_names [opcode] : "invalid opcode "),
         (opcode <= 7 ? 0 : opcode));

switch (opcode) {                                       /* dispatch the Starfish operation */

    case 000:                                           /* Start I/O Program */
        mem_read (&cpp_dev, absolute, MBX1, &opB);      /* read the channel/device word */
        mem_read (&cpp_dev, absolute, MBX3, &opA);      /*   and the channel program pointer */
        result = cpp_io_op (opcode, opA, opB);          /* execute an SIOP instruction */
        break;


    case 001:                                           /* Halt I/O Program */
        mem_read (&cpp_dev, absolute, MBX1, &opA);      /* read the channel/device word */
        result = cpp_io_op (opcode, opA, 0);            /*   and execute an HIOP instruction */
        break;


    case 002:                                           /* Read I/O Channel */
        mem_read (&cpp_dev, absolute, MBX1, &opA);      /* read the IMB read command word */
        result = cpp_io_op (opcode, opA, 0);            /*   and execute a RIOC instruction */

        mem_write (&cpp_dev, absolute, MBX2, result.data);  /* write the returned read value */
        break;


    case 003:                                           /* Write I/O Channel */
        mem_read (&cpp_dev, absolute, MBX1, &opB);      /* read the IMB write command word */
        mem_read (&cpp_dev, absolute, MBX2, &opA);      /*   and the data word to be written */
        result = cpp_io_op (opcode, opA, opB);          /* execute a WIOC instruction */
        break;


    case 004:                                           /* Enable/Disable CPP interrupts */
        mem_read (&cpp_dev, absolute, MBX1, &opA);      /* read the interrupt status word */
        interrupt_enable = opA & 1;                     /*   and mask to the LSB */

        result.condition = STATUS_CCE;                  /* the instruction always succeeds */

        if (interrupt_enable && imb_interrupt_request_set)  /* if a pending interrupt is now enabled */
            cpp_request = CPP_DELAY;                        /*   then reschedule the CPP to handle it */
        break;


    case 005:                                           /* I/O Clear */
        result = cpp_io_op (opcode, 0, 0);              /* execute an IOCL instruction */

        poll = imb_io_cycle (ROCL, 0, 0);               /* get the set of connected channels */

        interrupt_mask = poll;                          /* reenable all connected channels */
        imb_io_cycle (SMSK, 0, interrupt_mask);         /*   and send the SMSK command to the IMB */

        while (poll != 0) {                             /* loop through the channels */
            channel = HP_BIT_NUMBER (poll);             /*   and get the next channel number */

            setup_channel (channel);                    /* set up the channel */

            poll ^= HP_BIT (channel);                   /* clear the current bit */
            }

        interrupt_enable = TRUE;                        /* enable the interrupt system */
        break;


    case 006:                                           /* Initialize I/O Channel */
        mem_read (&cpp_dev, absolute, MBX1, &opA);      /* read the channel number word */
        result = cpp_io_op (opcode, opA, 0);            /*   and execute an INIT instruction */

        interrupt_mask &= ~HP_BIT (IO_CHANNEL (opA));    /* clear the channel bit in the interrupt mask */
        imb_io_cycle (SMSK, 0, interrupt_mask);         /*   and send the SMSK command to the IMB */
        break;


    case 007:                                           /* Initiate Self-Test */
        mem_write (&cpp_dev, absolute, MBX2, 0);        /* clear the self-test status */
        delay = SELF_TEST_DELAY;                        /*   and schedule the test completion */

        result.condition = STATUS_CCE;                  /* the instruction always succeeds */
        break;


    case 012:                                           /* unknown opcode */
        if (TRACING (cpp_dev, TRACE_DATA)) {            /* if memory tracing is active */
            mem_read (&cpp_dev, absolute, MBX1, &mask); /*   then perform */
            mem_read (&cpp_dev, absolute, MBX2, &mask); /*     dummy reads */
            mem_read (&cpp_dev, absolute, MBX3, &mask); /*       on the mailbox locations */
            mem_read (&cpp_dev, absolute, MBX4, &mask); /*         to document the parameters */
            }

        result.condition = STATUS_CCE;                  /* the instruction must succeed */
        break;


    default:                                            /* all other opcodes are illegal */
        result.condition = STATUS_CCI;                  /* set the invalid status indicator */
        break;
    }

Mailbox_Status = MBX_COMPLETE;                          /* set the completion bit */

if (result.condition == STATUS_CCI)                     /* if the opcode is invalid */
    Mailbox_Status |= MBX_OPCODE | MBX_ERROR;           /*   then add the invalid instruction opcode bit */

else if (result.condition == STATUS_CCG)                /* otherwise if the instruction failed */
    if (opcode == 000)                                  /*   then if it's an SIOP instruction */
        Mailbox_Status |= MBX_RUNNING | MBX_ERROR;      /*     then add the channel program not halted bit */
    else if (opcode == 001)                             /*   otherwise if it's an HIOP instruction */
        Mailbox_Status |= MBX_PENDING | MBX_ERROR;      /*     then add the halt is pending bit */
    else                                                /*   otherwise it must be an INIT instruction */
        Mailbox_Status |= MBX_ONLINE | MBX_ERROR;       /*     so add the system controller is offline bit */

else if (result.condition == STATUS_CCL)                /* otherwise if an IMB failure occurred */
    Mailbox_Status |= MBX_DNV | MBX_ERROR;              /*   then add the data not valid bit */

activate_unit (&Completion_Unit, delay);                /* schedule the operation completion event */
return;                                                 /*   and return to wait for it */
}


/* Set up a channel after initialization.

   Sending an IOCL or INIT command to the GIC will reset the PHI to its power-on
   state.  After that, the PHI must be set up as the controller-in-charge before
   it can be used to control the HP-IB.

   After verifying that the channel is a GIC and that the PHI has assumed the
   system controller function after power-on reset, the PHI is put online and
   then IFC is asserted to make the PHI the controller-in-charge.  If this did
   not occur because switch S3 (SYSCTRL) is in the OFF position, the routine
   sets the PHI offline again and returns CCG status.  Otherwise, the PHI
   remains online as the CIC, and the routine returns CCE.


   Implementation notes:

    1. The Series 6x microcode sets the "Respond to Parallel Poll" bit in the
       Control register before placing the PHI online and asserting IFC.
       However, IFC causes the PHI to become the controller, which always uses
       bus address 30, so this action apparently has no effect.
*/

static HP_WORD setup_channel (uint32 channel)
{
IMB_DATA response;
HP_WORD  condition;

response = imb_io_read (channel, Reg_E);                /* read the channel configuration register */

if (RE_CHANNEL_ID (response) != GIC_ID)                 /* if the channel is not a GIC */
    condition = STATUS_CCG;                             /*   then set CCG and quit */

else {                                                  /* otherwise */
    response = imb_io_read (channel, Reg_1);            /*   read the PHI status register */

    if (not (response & R1_SYSCNTLR))                   /* if the channel is not the system controller */
        condition = STATUS_CCG;                         /*   then set CCG and quit */

    else {                                              /* otherwise */
        imb_io_write (channel, Reg_6, R6_POLL);         /*   enable the parallel poll response */
        imb_io_write (channel, Reg_7, R7_ONLINE);       /*     and set the PHI online */

        imb_io_write (channel, Reg_6, R6_IFC | R6_REN); /* assert Interface Clear and Remote Enable */
        imb_io_write (channel, Reg_6, R6_REN);          /* deny IFC and leave REN asserted */

        imb_io_write (channel, Reg_2, R2_EVENTS);       /* clear any interrupt events */

        response = imb_io_read (channel, Reg_1);        /* reread the PHI status register */

        if (not (response & R1_CNTLR)) {                /* if the channel is not the controller */
            imb_io_write (channel, Reg_7, R7_OFFLINE);  /*   then set the PHI offline */
            condition = STATUS_CCG;                     /*     and set CCG */
            }

        else                                            /* otherwise the channel is the controller */
            condition = STATUS_CCE;                     /*   so set CCE */
        }
    }

return condition;                                       /* return the resulting condition code */
}


/* Process a channel service request.

   This routine is called by the CPP service routine to process a channel
   request.  Controllers request service by asserting CSRQ on the Intermodule
   Bus.  If the controller making the request is not the IMBA, this routine is
   called to handle it.

   On entry, the "channel" parameter is the number of the highest-priority
   channel requesting service.  Processing begins by issuing an OBSI command to
   determine the reason for the request.  If the request is spurious, the
   routine returns with nothing done.  Otherwise, the highest-priority device on
   the channel that is requesting service is determined, and the address of its
   DRT entry is obtained.

   An initial check is made to determine if a channel timeout or DMA abort
   occurred.  If so, the channel program is aborted, with the latter saving the
   DMA abort address in CPVA locations 4 and 5 before returning.  If the OBSI
   reports a valid request but neither the channel request nor device request
   bit is set, the channel program is aborted for an illegal request.

   If the preliminary checks pass, the channel status word is obtained from
   DRT word 3, and the current program state in bits 0 and 1 determines the
   course of action, as follows:

    1. If the program is stopped, then abort the channel program with an illegal
       CSRQ error.

    2. If the program is stopping, then:

       2a. If this is a device request, and the program is not waiting for a
           channel condition (DRT3 bits 12-14 are clear), then halt program
           execution; otherwise, abort the channel program with an illegal CSRQ
           error.

       2b. This is a channel request, so if the program is not waiting for a
           channel condition (DRT3 bits 12-14 are clear), then clean up and halt
           program execution.

       2c. If the program is waiting for DMA completion (DRT3 bit 14 is set),
           then clean up DMA and fetch the next channel instruction, as the
           program cannot be halted at this point.

       2d. If the program is waiting for device status or FIFO data (DRT bit 12
           or 13 is set), then write the device number to Register F, unmask all
           PHI interrupts, and fetch the next channel instruction, as the
           program cannot be halted at this point.

    3. If the program is starting, then:

       3a. Set SIOP to the channel with bit 12 set to clear the CSRQ.

       3b. If the channel is the controller, then assert REN and enable parity
           freeze, and then unmask the parallel poll response for the device.

       3c. Set the channel program state to "running," write the device number
           to Register F, unmask all PHI interrupts, and fetch the next
           instruction.

    4. If the program is running, then:

       4a. If the program is in the general wait state or is not waiting for a
           channel condition, then unmask all PHI interrupts, clear the wait
           state, and fetch the next instruction.

       4b. If this is a device request, and the program is waiting for a channel
           condition (DRT3 bit 12, 13, or 14 is set), then abort the channel
           program with an illegal CSRQ error.

       4c. If this is a channel request, and the program is waiting for DMA
           completion (DRT3 bit 14 is set), then clean up after the transfer and
           fetch the next instruction.  Otherwise, write the device number to
           Register F, unmask all PHI interrupts, and fetch the next
           instruction.


   Implementation notes:

    1. Only the Series 37/6x/7x can have the Device Reference Table outside of
       memory bank 0.  As we don't support those CPUs, we declare the DRT bank
       and offset to be constant.

    2. The DMA completion routine performs one of three actions before
       returning.  Either it continues program execution (after a non-chained
       transfer), or it initiates the next DMA operation (for a chained
       transfer), or it aborts the program (for a chain error).  Consequently,
       we return directly after calling the routine, as the appropriate action
       has already been performed.
*/

static void process_channel (uint32 channel)
{
const HP_WORD bank   = 0;                       /* DRT base memory bank */
const HP_WORD offset = 0;                       /* DRT base memory offset */
IMB_DATA      device, response, service;
HP_WORD       drt0, drt2, drt3;
uint32        drt;

service = imb_io_cycle (OBSI, channel, 0);              /* issue an OBSI to get the reason for the service */

if (service & OBSI_INVALID)                             /* if the request is not valid */
    return;                                             /*   then processing is complete */

if (service & OBSI_DEVRQ                                /* if this is a device request */
  && imb_io_read (channel, Reg_1) & R1_CNTLR)           /*   and the channel is the controller */
    imb_io_write (channel, Reg_3, R3_UNMASK);           /*     then unmask all PHI interrupt status bits */

device = OBSI_DEVICE (service);                         /* get the requesting device */
drt = TO_PA (bank, offset + TO_DRT (service));          /*   and its associated DRT address */

if (service & OBSI_CHNRQ) {                             /* if this is a channel request */
    if (service & OBSI_TIMEOUT) {                       /*   then if the channel timed out */
        abort_program (channel, device,                 /*     then abort the program */
                       drt, CPA_TIMEOUT);
        return;
        }

    else if (service & OBSI_ABORT) {                    /*   otherwise if DMA aborted */
        mem_read (&cpp_dev, absolute, drt + 2, &drt2);  /*     then get the CPVA pointer */

        response = imb_io_read (channel, Reg_8);            /* save the DMA upper address */
        mem_write (&cpp_dev, bank_0, drt2 + 4, response);   /*   in CPVA 4 */

        response = imb_io_read (channel, Reg_9);            /* save the lower address */
        mem_write (&cpp_dev, bank_0, drt2 + 5, response);   /*   in CPVA 5 */

        response = imb_io_read (channel, Reg_B);        /* get the DMA status and report it */
        abort_program (channel, device, drt,            /*   with the error code */
                       TO_CPA_DMA_ABORT (response));    /*     and abort the program */
        return;
        }
    }

else if (not (service & OBSI_DEVRQ)) {                  /* otherwise if it's not a device request */
    abort_program (channel, device,                     /*   then this is an illegal request */
                   drt, CPA_ILLEGAL);                   /*     so abort the program */
    return;
    }


mem_read (&cpp_dev, absolute, drt + 3, &drt3);          /* read the channel program status word */

if ((drt3 & CPS_EXEC_MASK) == CPS_STOPPED) {            /* if the program is stopped */
    abort_program (channel, device,                     /*   then abort the program */
                   drt, CPA_ILLEGAL);                   /*     with an illegal entry error */
    return;
    }


else if ((drt3 & CPS_EXEC_MASK) == CPS_STOPPING)        /* otherwise if the program is stopping */
    if (service & OBSI_DEVRQ) {                         /*   then if this is a device request */
        if ((drt3 & CPS_WAIT_MASK) == CPS_NOWAIT)       /*     then if the program is not waiting */
            halt_program (channel, device, drt);        /*       then halt the program */
        else                                            /*     otherwise */
            abort_program (channel, device,             /*       abort with an illegal condition */
                           drt, CPA_NCILLEGAL);         /*         but with no bus cleanup */

        return;
        }

    else if ((drt3 & CPS_WAIT_MASK) == CPS_NOWAIT) {    /* otherwise it's a channel request; if not waiting */
        cleanup_program (channel, device, drt);         /*   then clean up and halt the program */
        return;
        }

    else if (drt3 & CPS_DMAWAIT) {                      /* otherwise if waiting for DMA completion */
        drt3 &= CPS_STATE_MASK;                         /*   then clear the wait states */
        mem_write (&cpp_dev, absolute, drt + 3, drt3);  /*     and update the DRT */

        complete_dma (channel, device, drt);            /* complete the DMA transfer */
        return;
        }

    else {                                              /* otherwise it's halting while waiting for condition */
        imb_io_write (channel, Reg_F, device);          /*   so store the device number */
        imb_io_write (channel, Reg_3, R3_UNMASK);       /*     and unmask all PHI interrupt status bits */

        drt3 &= CPS_STATE_MASK;                         /* clear the wait states */
        mem_write (&cpp_dev, absolute, drt + 3, drt3);  /*   and update the DRT */
        }                                               /*     and fetch the next instruction */


else if ((drt3 & CPS_EXEC_MASK) == CPS_STARTING) {      /* otherwise if the program is starting */
    imb_io_cycle (SIOP, channel,                        /*   then clear the new status request */
                  device | SIOP_CLEAR_STATUS);

    if (imb_io_read (channel, Reg_1) & R1_CNTLR) {          /* if the channel is the controller */
        imb_io_write (channel, Reg_6, R6_FREEZE | R6_REN);  /*   then enable parity freeze and assert REN */

        response = imb_io_read (channel, Reg_4);        /* get the parallel poll response mask */

        response |= HPIB_BIT (device);                  /* unmask the device's poll response */
        imb_io_write (channel, Reg_4, response);        /*   and restore it */
        }

    imb_io_write (channel, Reg_F, device);              /* write device to register 15 */
    imb_io_write (channel, Reg_3, R3_UNMASK);           /*   and unmask all PHI interrupt status bits */

    drt3 = CPS_RUNNING;                                 /* set the running state */
    mem_write (&cpp_dev, absolute, drt + 3, drt3);      /*   and update the DRT */
    }                                                   /*     and fetch the next instruction */


else                                                    /* otherwise the program is running */
    if (drt3 & CPS_PROGWAIT                             /*   so if it is waiting */
      || not (drt3 & CPS_WAIT_MASK)) {                  /*     or running free */
        imb_io_write (channel, Reg_3, R3_UNMASK);       /*       then unmask all PHI interrupt status bits */

        drt3 &= CPS_STATE_MASK;                         /* clear the wait states */
        mem_write (&cpp_dev, absolute, drt + 3, drt3);  /*   and update the DRT */
        }                                               /*     and fetch the next instruction */

    else if (service & OBSI_DEVRQ) {                    /* otherwise if this is a device request */
        abort_program (channel, device,                 /*   then abort with an illegal condition */
                       drt, CPA_ILLEGAL);
        return;
        }

    else if (drt3 & CPS_DMAWAIT) {                      /* otherwise if waiting for DMA completion */
        drt3 &= CPS_STATE_MASK;                         /*   then clear the wait states */
        mem_write (&cpp_dev, absolute, drt + 3, drt3);  /*     and update the DRT */

        complete_dma (channel, device, drt);            /* complete the DMA transfer */
        return;
        }

    else {                                              /* otherwise we are waiting for a condition */
        imb_io_write (channel, Reg_F, device);          /*   so write the device number to register 15 */
        imb_io_write (channel, Reg_3, R3_UNMASK);       /*     and then unmask all PHI interrupt status bits */

        drt3 &= CPS_STATE_MASK;                         /* clear the wait states */
        mem_write (&cpp_dev, absolute, drt + 3, drt3);  /*   and update the DRT */
        }                                               /*     and fetch the next instruction */

mem_read (&cpp_dev, absolute, drt + 0, &drt0);          /* read the channel program pointer */
execute_program (channel, device, drt, drt0);           /*   and start or resume the program */

return;                                                 /* return with the request processed */
}


/* Abort the channel program.

   This routine is called to abort a channel program due to an error.  On entry,
   the "channel" parameter contains the channel number, "device" contains the
   device number on the channel, "drt" contains the address of the DRT entry,
   and "code" contains the error code causing the abort.  If the "no cleanup"
   bit is set in the code, the routine simply halts the program.  Otherwise, it
   cleans up the PHI before halting.

   The routine first checks the CPVA address.  If it is less than 40 octal, then
   no interrupt is generated, as storing the error code for the interrupt
   handler would write into the CPU reserved memory area.
*/

static void abort_program (uint32 channel, uint32 device, uint32 drt, uint32 code)
{
HP_WORD drt2;

tprintf (cpp_dev, TRACE_CMD, "Channel %u program aborted with code %06o\n",
         channel, code);

mem_read (&cpp_dev, absolute, drt + 2, &drt2);          /* get the CPVA pointer */

if (drt2 >= RESERVED_MEMORY) {                          /* if the area is above reserved memory */
    mem_write (&cpp_dev, bank_0, drt2 + 0, code);       /*   then write the error code to CPVA 0 */
    imb_io_write (channel, Reg_C, device | RC_SET_IRQ); /*     and request a device interrupt */
    }

if (code & CPA_NOCLEANUP)                               /* if a no-cleanup abort is requested */
    halt_program (channel, device, drt);                /*   then simply halt the program */
else                                                    /* otherwise */
    cleanup_program (channel, device, drt);             /*   clean up the PHI and then halt */

return;
}


/* Clean up the PHI for program termination.

   This routine is called to clean up the PHI configuration before stopping a
   channel program.  On entry, the "channel" parameter contains the channel
   number, "device" contains the device number on the channel, and "drt"
   contains the address of the DRT entry.

   First, the DMA configuration is reset, keeping just the byte select, EOI
   select, and transfer direction bits.  Then all PHI interrupts are unmasked.
   If the PHI is the controller, all interrupt conditions are cleared, the FIFOs
   are cleared of any retained data, and a Serial Poll Disable is sent to the
   bus.  If the PHI is not the controller, then clear the PHI configuration
   except for the Parity Freeze setting.

   The channel program is then halted.
*/

static void cleanup_program (uint32 channel, uint32 device, uint32 drt)
{
IMB_DATA response;

response = imb_io_read (channel, Reg_B)                     /* get the DMA configuration */
             & (RB_RIGHT_BYTE | RB_NO_EOI | RB_DIRECTION);  /*   and mask to just the R/E/D bits */

imb_io_write (channel, Reg_F, response | device);       /* update the configuration */

imb_io_write (channel, Reg_3, R3_UNMASK);               /* unmask all PHI interrupt status bits */

if (imb_io_read (channel, Reg_1) & R1_CNTLR) {          /* if the channel is the controller */
    imb_io_write (channel, Reg_2, R2_EVENTS);           /*   then clear any interrupt events */

    clear_fifos (channel);                              /* clear the PHI FIFOs */

    imb_io_write (channel, Reg_0, PHI_SPD);             /* issue a Serial Poll Disable */
    }

else {                                                      /* otherwise the channel is not the controller */
    response = imb_io_read (channel, Reg_6);                /*   then get the configuration */
    imb_io_write (channel, Reg_6, response & R6_FREEZE);    /*     and retain only the parity freeze */
    }

halt_program (channel, device, drt);                    /* halt the channel program */

return;
}


/* Halt the channel program.

   This routine is called to stop a channel program.  On entry, the "channel"
   parameter contains the channel number, "device" contains the device number on
   the channel, and "drt" contains the address of the DRT entry.

   First, the New Status register bit is cleared in case an HIOP instruction
   caused the halt.  The channel program status word is then cleared of all bits
   except the "power fail recovery in progress" bit.

   Assuming that the PHI is the controller, the device's poll response bit is
   masked to prevent the device from requesting service.  If unsent data remains
   in the outbound FIFO, then IFC is asserted to clear the bus protocol for the
   devices, and the FIFO is cleared.  All PHI interrupts are reset, and then
   interrupts are enabled for status changes and parallel poll responses.

   Before returning, any pending execution limit restart for the device is
   cleared, and then the channel restart is cleared if no pending device
   restarts are left.


   Implementation notes:

    1. In hardware, if the outbound FIFO still contains data when the program
       is to be halted, the microcode delays up 20 microseconds to allow the
       FIFO to empty.  If it is not empty at the end of that time, an IFC is
       done, and the FIFO is cleared.  To model that in simulation, we would
       have to run the event pump in a loop here; otherwise, a device
       transferring data would not receive event service calls.  We could do
       this, but then we would have to return the "sim_process_event" call
       status, in case an IO error occurred.

       Because this routine is called from several places, each of those would
       have to propagate the status upward until it was eventually returned from
       "cpp_service" to the CPU's instruction execution loop.  This involves too
       much clutter for the odd case where this would actually be better than
       simply aborting the transfer with an IFC and FIFO clear.  So we elect to
       do the latter unilaterally.
*/

static void halt_program (uint32 channel, uint32 device, uint32 drt)
{
HP_WORD  drt3;
IMB_DATA response;

imb_io_cycle (HIOP, channel, device | HIOP_CLEAR_STATUS);   /* clear a potential new status request */

mem_read (&cpp_dev, absolute, drt + 3, &drt3);          /* read the channel program status word */

drt3 = drt3 & CPS_POWERFAIL;                            /* clear all status bits except power fail */
mem_write (&cpp_dev, absolute, drt + 3, drt3);          /*   and update the status word */

if (imb_io_read (channel, Reg_1) & R1_CNTLR) {          /* if the channel is the controller */
    response = imb_io_read (channel, Reg_4)             /*   then get the parallel poll response mask */
                 & ~HPIB_BIT (device);                  /*     and clear the device's response bit */

    imb_io_write (channel, Reg_4, response);            /* restore the poll response mask */

    if (not (imb_io_read (channel, Reg_2) & R2_EMPTY)) {    /* if data remains in the outbound FIFO */
        response = imb_io_read (channel, Reg_6);            /*   then get the control configuration */
        response = response & R6_FREEZE | R6_REN;           /*     and keep only the parity freeze setting */

        imb_io_write (channel, Reg_6, R6_IFC | response);   /* assert IFC to clear the bus */
        imb_io_write (channel, Reg_6, R6_REN | R6_CLEAR);   /*   and then deny IFC and clear the FIFO  */

        imb_io_write (channel, Reg_2, R2_EVENTS);       /* clear any interrupt events */
        }

    imb_io_write (channel, Reg_3,                       /* unmask the status change */
                  R3_ENABLE | R3_STATUS | R3_POLL);     /*   and parallel poll interrupts */
    }

restart_set [channel] &= ~BIT (device);                 /* clear any pending execution limit bit */

if (restart_set [channel] == 0)                         /* if all device limit bits are clear */
    execution_limit_set &= ~BIT (channel);              /*   then clear any pending channel limit bit */

return;
}


/* Suspend the channel program.

   This routine is called to suspend a channel program.  When the outbound FIFO
   must be empty before the channel program can proceed, a check is mode by
   reading Register 2.  If the FIFO Empty bit is set, then the program
   continues.  Otherwise, the program is suspended by calling this routine.

   On entry, the "channel" parameter contains the channel number, "device"
   contains the device number on the channel, "drt" contains the address of
   the DRT entry, and "status" contains the status word from Register 2.

   First, the status word is checked for the presence of a parity error, status
   change, handshake abort, or Device Clear indication.  If any of these are
   detected, the channel program is aborted.  If no errors are present, then the
   PHI FIFO Empty interrupt is unmasked, and parallel poll recognition is
   disabled.  The FIFO Wait bit is set in the program status word in DRT
   location 3 before returning to the caller.
*/

static void suspend_program (uint32 channel, uint32 device, uint32 drt, HP_WORD status)
{
HP_WORD drt3;
uint32  error = 0;

if (status & R2_EVENTS) {                               /* if one or more interrupt events are present */
    if (status & R2_PARITY)                             /*   then if the parity bit is set */
        error |= CPA_BPARITY;                           /*     then set the bus command parity error code */

    if (status & R2_STATUS)                             /* if the status change bit is set */
        error |= CPA_CHANGE;                            /*   then set the status has changed code */

    if (status & R2_HSABORT)                            /* if the handshake bit is set */
        error |= CPA_FIFO;                              /*   then set the FIFO handshake aborted code */

    if (status & R2_DCL)                                /* if a Device Clear command was received */
        error |= CPA_DCL;                               /*   then set the DCL received code */

    abort_program (channel, device, drt, error);        /* abort the channel program */
    }

else {                                                      /* otherwise the FIFO is occupied */
    imb_io_write (channel, Reg_3, R3_ENABLE | R3_EMPTY);    /*   so unmask the empty interrupt */
    imb_io_write (channel, Reg_F, RF_NO_POLL | device);     /*     and disable parallel poll detection */

    mem_read (&cpp_dev, absolute, drt + 3, &drt3);      /* get the status word */
    drt3 = drt3 & CPS_STATE_MASK | CPS_FIFOWAIT;        /*   and set the FIFO waiting bit */
    mem_write (&cpp_dev, absolute, drt + 3, drt3);      /*     and update the DRT */
    }

return;                                                 /* return to abort or wait for the interrupt */
}


/* Execute the channel program.

   This routine is called to execute the channel program.  Starting with the
   instruction indicated by the channel program pointer in DRT location 0, the
   routine executes instructions until until one of three conditions occur:

    1. The program waits for CSRQ.

    2. The program terminates with an Interrupt/Halt instruction.

    3. The program exceeds the execution limit.

   On entry, the "channel" parameter contains the channel number, "device"
   contains the device number on the channel, "drt" contains the address of
   the DRT entry, and "drt0" contains the channel program pointer.  The routine
   then enters an execution loop.  First, the instruction and its operand are
   read.  The latter is done because all instructions are at least two words
   long.  Then the opcode is used to dispatch to the appropriate instruction
   executor.

   To prevent a program containing an infinite loop from hanging the CPP, a
   limit is imposed on the number of consecutive instructions allowed before
   execution is suspended.  If that limit is reached, a bit is added to the set
   of suspended channels, and a bit is added to the set of suspended devices for
   that channel.  These sets are used to restart execution after a delay to
   allow the CPU and CPP to execute machine instructions and other channel
   programs, respectively.  Restarts have a lower priority than new interrupt or
   channel service requests.


   Implementation notes:

    1. The channel program pointer passed in the "drt0" parameter is masked to
       the address range on entry.  This allows the callers to calculate address
       offsets without masking.

    2. The Series 6x microcode listing manual seems to show that channel
       programs yield only when waiting for CSRQ or when a Halt is executed.
       That is, an infinite loop, e.g., JUMP *+1 / JUMP *-1 sequence, should
       hang the CPP and be unstoppable because it does not yield.  Testing on a
       Series 37 shows that this does not occur, however, so some unknown
       mechanism prevents this.  In simulation, the execution limit is used to
       produce the proper effect.

    3. The Series 6x microcode listing manual shows that the Identify command
       waits for a FIFO empty interrupt after sending the Amigo ID sequence.
       Address 097E on page 192 shows that ALU A does a CF2 (clear flag #2)
       micro-order before calling the SUSB routine to suspend the channel
       program.  SUSB on page 218 shows that Register 3 (interrupt mask) is
       configured to enable the outbound FIFO empty interrupt if F2 is clear or
       enable inbound FIFO data available interrupt if F2 is set.  When the ID
       sequence clears the outbound FIFO, the Identify command is reentered.  It
       then checks the inbound FIFO data available bit in Register 2.  If it is
       set, the ID bytes are read.  Otherwise, the ID sequence is output again.

       This is clearly an error; the Identify command should be doing SF2
       instead of CF2.  Presumably, it works because the connected device
       returns its ID bytes immediately after receiving the end of the ID
       sequence, so although the CPP is reentered when the FIFO empties, the
       data reception beats the test for data availability. However, if no
       device is present at the ID address, then the CPP resends the ID sequence
       repeatedly until commanded to stop (e.g., by the IOMAP program) or a GIC
       timeout occurs.

       This error is corrected in the implementation below.

    4. The Series 6x microcode listing manual shows that the Write command
       enables the PHI outbound FIFO empty interrupt immediately before
       configuring and starting an outbound DMA transfer.  As DMA attempts to
       keep the FIFO full, the transfer will complete before the device has
       accepted the final bytes.  The DMA interrupt that asserts CSRQ is
       therefore held off until PHI interrupt assertion indicates that the FIFO
       has emptied.

       If the device had accepted the Talk command and associated secondary that
       were written to the FIFO immediately prior to the interrupt enable
       command, then the PHI FIFO would be empty until DMA transferred the
       first byte into it, and the PHI interrupt would assert CSRQ.  PHI
       interrupts are inhibited from asserting CSRQ once DMA is running, so that
       early CSRQ would persist only for the short time between the writes that
       enable interrupts and then start DMA.

       In hardware, this transient CSRQ seems to be too short to be recognized.
       In simulation, however, this early CSRQ is taken as the end-of-transfer
       CSRQ, disrupting channel program execution.  Therefore, we reverse the
       order of the writes to Register 3 (interrupt enable) and Register B
       (start DMA), so that the interrupt is not enabled until after DMA has
       filled the outbound FIFO.

    5. The Series 6x microcode issues an Unlisten and Untalk during execution of
       an Interrupt/Halt instruction.  These are redundant, as execution of the
       prior instruction leaves the bus unaddressed.  However, the comments in
       the microcode CLFF routine says, "The reason to [unaddress] is to assure
       that the HP-IB data is not mistaken for commands when clearing the
       outbound FIfO when IFC is not used".  The simulation follows this
       behavior.
*/

static void execute_program (uint32 channel, uint32 device, uint32 drt, HP_WORD drt0)
{
IMB_DATA  response;
CP_OPCODE opcode;
IMB_DATA  mode;
HP_WORD   drt2, drt3, drtz, instruction, operand, base, burst, term_burst, flags;
HP_WORD   dsj, max, id, reg, mask, data, byte, cmd, count, bank, offset, chain;
t_bool    executing = TRUE;                     /* TRUE if instruction execution should continue */
uint32    execution_count = 10;                 /* limit of consecutive instructions executed */
uint32    secondary = 0;                        /* bus secondary for Read and Write instructions */

while (executing) {                                         /* execute instructions until a yield occurs */
    mem_read (&cpp_dev, bank_0, drt0 + 0, &instruction);    /* read the instruction word */
    mem_read (&cpp_dev, bank_0, drt0 + 1, &operand);        /*   and its operand word */

    opcode = CPI_OPCODE (instruction);                  /* get the instruction opcode */

    if (TRACING (cpp_dev, TRACE_CMD))
        trace_instruction (channel, instruction, operand, drt0);

    switch (opcode) {                                   /* dispatch the instruction opcode */

        case Relative_Jump:                             /* Relative Jump */
            drt0 = drt0 + operand & LA_MASK;            /* adjust the pointer by the +/- displacement */
            break;


        case Interrupt:                                     /* Interrupt Halt/Continue */
            if (instruction & CPI_HALT) {                   /* if the program is halting */
                response = imb_io_read (channel, Reg_2);    /*   then read the interrupt conditions */

                if (not (response & R2_EMPTY)) {                        /* if the outbound FIFO is not empty */
                    suspend_program (channel, device, drt, response);   /*   then suspend the program */
                    executing = FALSE;                                  /*     until the all bytes are sent */
                    break;
                    }
                }

            mem_read (&cpp_dev, absolute, drt + 2, &drt2);  /* get the CPVA pointer */

            mem_write (&cpp_dev, bank_0, drt2 + CPI_CPVA (instruction), /* write the interrupt code */
                       TO_CPA_INTERRUPT (operand));                     /*   to the indicated CPVA entry */

            imb_io_write (channel, Reg_C, device | RC_SET_IRQ); /* request an interrupt for the device */

            if (instruction & CPI_HALT) {               /* if the program is halting */
                cleanup_program (channel, device, drt); /*   then clean up and halt execution */

                mem_read (&cpp_dev, absolute, drt + 3, &drt3);  /* get the status word */
                drt3 = drt3 & CPS_POWERFAIL;                    /*   and clear all bits except power fail */
                mem_write (&cpp_dev, absolute, drt + 3, drt3);  /*     and update the DRT */

                drt0 = drt0 + 2;                        /* bump the channel program pointer */
                executing = FALSE;                      /*   and terminate execution */
                }
            break;


        case Wait:                                          /* Wait */
            mem_read (&cpp_dev, absolute, drt + 3, &drt3);  /* get the channel status word */

            if (drt3 & CPS_RUNNING) {                           /* if the program is running */
                drt3 = drt3 & CPS_STATE_MASK | CPS_PROGWAIT;    /*   then set the wait bit */
                mem_write (&cpp_dev, absolute, drt + 3, drt3);  /*     and write it back */

                if (instruction & CPI_WAIT)             /* if waiting for PHI conditions */
                    imb_io_write (channel, Reg_F,       /*   then inhibit parallel poll recognition */
                                  RF_NO_POLL | device);

                else {                                          /* otherwise */
                    response = imb_io_read (channel, Reg_1);    /*   get the PHI status */

                    if (response & R1_CNTLR) {                      /* if the PHI is the controller */
                        response = imb_io_read (channel, Reg_2);    /*   then get the interrupt conditions */

                        if (response & R2_EMPTY)            /* if the outbound FIFO is empty */
                            imb_io_write (channel, Reg_3,   /*   then unmask status changes and poll responses */
                                          R3_ENABLE | R3_STATUS | R3_POLL);
                        }
                    }
                }

            else                                        /* otherwise the program is not running */
                abort_program (channel, device,         /*   so report that an HIOP was issued */
                               drt, CPA_HIOP);          /*     during active service */

            drt0 = drt0 + 2;                            /* bump the channel program pointer */
            executing = FALSE;                          /*   and terminate execution */
            break;


        case Read_Control:                              /* Read Control */
        case Write_Control:                             /* Write Control */
            secondary = 16;                             /* the control instructions add 16 to the secondary */

        /* fall through into the Read/Write case */

        case Read:                                              /* Read */
        case Write:                                             /* Write */
            secondary = secondary + CPI_MODIFIER (instruction); /* form the secondary from the modifier */

            response = imb_io_read (channel, Reg_2);    /* read the PHI conditions */

            if (not (response & R2_EMPTY)) {                        /* if the outbound FIFO is not empty */
                suspend_program (channel, device, drt, response);   /*   then suspend the program */
                executing = FALSE;                                  /*     until the all bytes are sent */
                }

            else {                                      /* otherwise the FIFO is empty */
                drtz = drt0;                            /*   so copy the current instruction pointer */

                chain = CPI_DATA_CHAIN (instruction);   /* get the current chain number */

                if (operand == 0)                       /* if the byte count is zero */
                    if (chain == 0) {                   /*   then if not chained */
                        abort_program (channel, device, /*     then abort the program */
                                       drt, CPA_CHAIN); /*       with a data chain error */
                        executing = FALSE;
                        break;
                        }

                    else {                              /* otherwise */
                        drtz = drt0 + chain * 5;        /*   point at the current chain entry */

                        mem_read (&cpp_dev, bank_0,     /* read the next chain operand */
                                  drtz + 1, &operand);

                        if (operand == 0) {                 /* if the next byte count is zero */
                            abort_program (channel, device, /*     then abort the program */
                                           drt, CPA_CHAIN); /*       with a data chain error */
                            executing = FALSE;
                            break;
                            }

                        else {                                      /* otherwise */
                            mem_read (&cpp_dev, bank_0,             /*   get the next instruction word */
                                      drtz + 0, &instruction);      /*     and the chain number */
                            chain = CPI_DATA_CHAIN (instruction);   /*       from the instruction */
                            }

                        if (TRACING (cpp_dev, TRACE_CMD))
                            trace_instruction (channel, instruction, operand, drtz);
                        }

                mem_read (&cpp_dev, bank_0, drtz + 2, &term_burst); /* get the burst word */
                mem_read (&cpp_dev, bank_0, drtz + 3, &flags);      /*   and the flags word */

                burst = CPI_BURST_LEN (term_burst);     /* get the burst length */

                if (burst == 0)                         /* if the burst length is zero */
                    burst = 256;                        /*   then reset to 256 bytes */

                if (opcode == Read || opcode == Read_Control) { /* if this is a read operation */
                    flags &= ~CPI_DIRECTION;                    /*   then the DMA direction is inbound */

                    if (not (response & R2_DATA)) {                                 /* if the inbound FIFO is empty */
                        imb_io_write (channel, Reg_0, PHI_LISTEN    | controller);  /*   send Listen <controller> */
                        imb_io_write (channel, Reg_0, PHI_TALK      | device);      /*     and Talk <device> */
                        imb_io_write (channel, Reg_0, PHI_SECONDARY | secondary);   /*     and the secondary */

                        secondary = 0;                  /* clear the secondary for the next entry */

                        if (flags & CPI_BURST_MODE && burst == 1) { /* if this is a single-byte burst */
                            if (flags & CPI_TERM_LF)                /*   then if ending on LF reception */
                                mode = R0_MODE_LF_COUNTED | 1;      /*     then receive one byte with LF termination */
                            else                                    /*   otherwise */
                                mode = R0_MODE_COUNTED | 1;         /*     receive one byte */

                            imb_io_write (channel, Reg_0, mode);    /* deny ATN and enable reception */

                            suspend_for_data (channel, device, drt);    /* set up the inbound data wait */
                            executing = FALSE;                          /*   and exit to wait for the data byte */
                            }

                        else {                              /* otherwise */
                            if (flags & CPI_BURST_MODE) {   /*   if this is a multi-byte burst */
                                if (burst < operand)        /*     then set the transfer size to the smaller */
                                    operand = burst;        /*       of the burst or remaining byte count */

                                if (flags & CPI_TERM_LF)                    /* if ending on LF reception */
                                    mode = R0_MODE_LF_COUNTED | operand;    /*     then receive with LF termination */
                                else                                        /*   otherwise */
                                    mode = R0_MODE_COUNTED | operand;       /*     receive the count */
                                }

                            else                            /* otherwise it is record mode */
                                mode = R0_MODE_UNCOUNTED;   /*   so enable an uncounted reception */

                            imb_io_write (channel, Reg_0, mode);    /* deny ATN and enable reception */

                            initiate_dma (channel, device, drt,     /* setup and start */
                                          drtz, operand, flags);    /*   the DMA transfer */
                            executing = FALSE;                      /*     and wait until it completes */
                            }
                        }

                    else {                                      /* otherwise the FIFO contains data */
                        byte = imb_io_read (channel, Reg_0);    /*   so get the inbound byte */

                        bank = CPI_BANK (flags);                        /* get the target location bank */
                        mem_read (&cpp_dev, bank_0, drtz + 4, &offset); /*   and offset */

                        mem_read (&cpp_dev, absolute, TO_PA (bank, offset), &data); /* read the target word */

                        if (flags & CPI_RIGHT_BYTE)             /* if storing to the right-hand byte */
                            data = REPLACE_LOWER (data, byte);  /*   then replace the lower byte */
                        else                                    /* otherwise */
                            data = REPLACE_UPPER (data, byte);  /*   replace the upper byte */

                        mem_write (&cpp_dev, absolute, TO_PA (bank, offset), data); /* write the target word */

                        operand--;                      /* drop the byte count */

                        if (not (flags & CPI_NO_UPDATE)) {                      /* if not suppressing updates */
                            mem_write (&cpp_dev, bank_0, drtz + 1, operand);    /*   then update the byte count */

                            if (flags & CPI_RIGHT_BYTE) {       /* if we just wrote the right byte */
                                offset++;                       /*   then increment the address */
                                mem_write (&cpp_dev, bank_0,    /*     and write it back */
                                           drtz + 4, offset);
                                }

                            flags ^= CPI_RIGHT_BYTE;        /* flip the R bit for the next transfer */
                            mem_write (&cpp_dev, bank_0,    /*   and update the instruction */
                                       drtz + 3, flags);

                            if (offset == 0) {                      /* if the address rolled over */
                                abort_program (channel, device,     /*   then abort the program */
                                               drt, CPA_ROLLOVER);  /*     with a memory error */
                                executing = FALSE;
                                }
                            }

                        imb_io_write (channel, Reg_0, PHI_UNTALK);      /* send and Untalk and Unlisten */
                        imb_io_write (channel, Reg_0, PHI_UNLISTEN);    /*   to end the transfer */

                        if (operand == 0 && chain > 0                       /* if the current chain is complete */
                          && not update_chain (drt, drt0, instruction)) {   /*   and the chain update fails */
                            abort_program (channel, device,                 /*     then abort the program */
                                           drt, CPA_CHAIN);                 /*       with a data chain error */
                            executing = FALSE;
                            }

                        drt0 += chain * word_count [opcode];    /* point at the next instruction (* + 0) */

                        if (R0_MODE (byte) != R0_MODE_TAGGED)       /* if EOI did not end the transfer */
                            if (chain == 0)                         /*   then if this is the last or only chain */
                                drt0 += CPI_TERM_DISP (term_burst); /*     then continue at (* + TD) */
                            else                                    /*   otherwise */
                                drt0 += 2;                          /* continue at (* + 2) */
                        }
                    }

                else {                                  /* otherwise this is a write operation */
                    flags |= CPI_DIRECTION;             /*   so the DMA direction is outbound */

                    imb_io_write (channel, Reg_0, PHI_TALK      | controller);  /*   send Talk <controller> */
                    imb_io_write (channel, Reg_0, PHI_LISTEN    | device);      /*   and Listen <device> */
                    imb_io_write (channel, Reg_0, PHI_SECONDARY | secondary);   /*     and the secondary */

                    secondary = 0;                      /* clear the secondary for the next entry */

                    if (chain == 0                      /* if this is the last or only chain */
                      && not (flags & CPI_BURST_MODE    /*   and this is not the the final burst */
                      && operand > burst))              /*     of a burst-mode transfer */
                        flags &= ~CPI_NO_EOI;           /*       then tag it with EOI */

                    else if (chain > 0                  /* otherwise if this is an intermediate chain */
                      && not (flags & CPI_BURST_MODE))  /*   and in record mode */
                        flags |= CPI_NO_EOI;            /*     then suppress EOI */

                    if (flags & CPI_BURST_MODE && burst == 1) { /* if this is a single-byte burst */
                        bank = CPI_BANK (flags);                /*   then get the source location bank */
                        mem_read (&cpp_dev, bank_0,             /*     and offset */
                                  drtz + 4, &offset);

                        mem_read (&cpp_dev, absolute, TO_PA (bank, offset), &data); /* read the source word */

                        if (flags & CPI_RIGHT_BYTE)     /* if reading the right-hand byte */
                            data = LOWER_BYTE (data);   /*   then get the lower byte */
                        else                            /* otherwise */
                            data = UPPER_BYTE (data);   /*   get the upper byte */

                        if (not (flags & CPI_NO_EOI)        /* if EOI is not suppressed */
                          || operand == 1 && chain == 0)    /*   or it's the final byte of the transfer */
                            data |= R0_EOI;                 /*     then add the EOI */

                        imb_io_write (channel, Reg_0, data);    /* send the data byte */

                        operand--;                      /* drop the byte count */

                        if (not (flags & CPI_NO_UPDATE)) {                      /* if not suppressing updates */
                            mem_write (&cpp_dev, bank_0, drtz + 1, operand);    /*   then update the byte count */

                            if (flags & CPI_RIGHT_BYTE) {       /* if we just wrote the right byte */
                                offset++;                       /*   then increment the address */
                                mem_write (&cpp_dev, bank_0,    /*     and write it back */
                                           drtz + 4, offset);
                                }

                            flags ^= CPI_RIGHT_BYTE;        /* flip the R bit for the next transfer */
                            mem_write (&cpp_dev, bank_0,    /*   and update the instruction */
                                       drtz + 3, flags);

                            if (offset == 0) {                      /* if the address rolled over */
                                abort_program (channel, device,     /*   then abort the program */
                                               drt, CPA_ROLLOVER);  /*     with a memory error */
                                executing = FALSE;
                                }

                            else {                                                  /* otherwise */
                                if (operand == 0 && chain > 0                       /* if the current chain is complete */
                                  && not update_chain (drt, drt0, instruction)) {   /*   and the chain update fails */
                                    abort_program (channel, device,                 /*     then abort the program */
                                                   drt, CPA_CHAIN);                 /*       with a data chain error */
                                    executing = FALSE;
                                    }

                                drt0 += chain * word_count [opcode];    /* point at the next instruction (* + 0) */

                                if (operand > 0 || chain > 0)   /* if the transfer is incomplete */
                                    drt0 += 2;                  /*   then skip the next instruction (* + 2) */

                                imb_io_write (channel, Reg_0, PHI_UNLISTEN);        /* Unlisten */
                                imb_io_write (channel, Reg_6, R6_FREEZE | R6_REN);  /*   and enable parity freeze */
                                }
                            }
                        }

                    else {                              /* otherwise */
                        if (flags & CPI_BURST_MODE      /*   if this is a multi-byte burst */
                          && burst < operand)           /*     then set the transfer size to the smaller */
                            operand = burst;            /*       of the burst or remaining byte count */

                        response = imb_io_read (channel, Reg_6) /* mask the PHI configuration */
                                     & R6_FREEZE                /*   to just the parity freeze */
                                     | R6_REN | R6_OUTBOUND;    /*     and set REN and the DMA direction */

                        imb_io_write (channel, Reg_6, response);    /* rewrite the PHI configuration */

                        imb_io_write (channel, Reg_3, R3_ENABLE | R3_EMPTY);    /* unmask the empty interrupt */

                        initiate_dma (channel, device, drt,     /* setup and start */
                                      drtz, operand, flags);    /*   the DMA transfer */
                        executing = FALSE;                      /*     and wait until it completes */
                        }
                    }
                }
            break;


        case Device_Specified_Jump:                     /* Device Specified Jump */
            response = imb_io_read (channel, Reg_2);    /* read the PHI conditions */

            if (not (response & R2_EMPTY)) {            /* if the outbound FIFO is not empty */
                suspend_program (channel, device,       /*   then suspend the program */
                                 drt, response);        /*     until the all bytes are sent */
                executing = FALSE;
                }

            else if (not (response & R2_DATA)) {                        /* otherwise if the inbound FIFO is empty */
                imb_io_write (channel, Reg_0, PHI_LISTEN | controller); /*   then send Listen <controller> */
                imb_io_write (channel, Reg_0, PHI_TALK   | device);     /*     and Talk <device> */
                imb_io_write (channel, Reg_0, PHI_SECONDARY | 16);      /*     and Secondary 10H (DSJ) */
                imb_io_write (channel, Reg_0, R0_MODE_COUNTED | 1);     /*     and then deny ATN and receive one byte */

                suspend_for_data (channel, device, drt);    /* set up the inbound data wait */
                executing = FALSE;                          /*   and exit to wait for the DSJ byte */
                }

            else {                                          /* otherwise FIFO data is present */
                response = imb_io_read (channel, Reg_0);    /*   so read the DSJ byte */

                dsj = LOWER_BYTE (response);                    /* mask off the upper byte */
                mem_write (&cpp_dev, bank_0, drt0 + 1, dsj);    /*   and write it to memory */

                imb_io_write (channel, Reg_0, PHI_UNTALK);      /* send Untalk */
                imb_io_write (channel, Reg_0, PHI_UNLISTEN);    /*   and Unlisten */

                max = CPI_RESPONSE (instruction);       /* get the maximum expected response */

                base = drt0 + 2;                        /* point at the displacement table */
                drt0 = drt0 + max;                      /*   and at the following instruction */

                if (dsj <= max) {                       /* if the DSJ index is within the table */
                    mem_read (&cpp_dev, bank_0,         /*   then read the target displacement */
                              base + dsj, &operand);    /*     from the current instruction */
                    drt0 = drt0 + operand & LA_MASK;    /*       and point +/- at the target instruction */
                    }
                }
            break;


        case Identify:                                  /* Amigo Identify */
            response = imb_io_read (channel, Reg_2);    /* read the PHI conditions */

            if (not (response & R2_EMPTY)) {            /* if the outbound FIFO is not empty */
                suspend_program (channel, device,       /*   then suspend the program */
                                 drt, response);        /*     until the all bytes are sent */
                executing = FALSE;
                }

            else if (not (response & R2_DATA)) {                        /* otherwise if the inbound FIFO is empty */
                imb_io_write (channel, Reg_0, PHI_LISTEN | controller); /*   then send Listen 30 */
                imb_io_write (channel, Reg_0, PHI_UNTALK);              /*     and Untalk */
                imb_io_write (channel, Reg_0, PHI_SECONDARY | device);  /*     and the device's secondary address */
                imb_io_write (channel, Reg_0, R0_MODE_COUNTED | 2);     /*     and then deny ATN and receive two bytes */

                suspend_for_data (channel, device, drt);    /* set up the inbound data wait */
                executing = FALSE;                          /*   and exit to wait for the ID bytes */
                }

            else {                                      /* otherwise FIFO data is present */
                id = imb_io_read (channel, Reg_0);      /*   so read the first ID byte */

                response = imb_io_read (channel, Reg_0);    /* read the second ID byte */
                id = TO_WORD (id, response);                /*   and merge the two bytes */

                mem_write (&cpp_dev, bank_0, drt0 + 1, id); /* save the ID as the operand */

                imb_io_write (channel, Reg_0, PHI_TALK | controller);   /* end the ID sequence with Talk 30 */
                imb_io_write (channel, Reg_0, PHI_UNLISTEN);            /*   and send Unlisten to start poll */
                }
            break;


        case Clear:                                     /* Clear */
            response = imb_io_read (channel, Reg_2);    /* read the PHI conditions */

            if (not (response & R2_EMPTY)) {            /* if the outbound FIFO is not empty */
                suspend_program (channel, device,       /*   then suspend the program */
                                 drt, response);        /*     until the all bytes are sent */
                executing = FALSE;
                }

            else {                                      /* otherwise */
                operand = CPI_CONTROL (instruction);    /*   get the control byte */

                imb_io_write (channel, Reg_0, PHI_TALK   | controller); /* send Talk <controller> */
                imb_io_write (channel, Reg_0, PHI_LISTEN | device);     /*   and Listen <device> */
                imb_io_write (channel, Reg_0, PHI_SECONDARY | 020);     /*   and Amigo Clear */
                imb_io_write (channel, Reg_0, R0_EOI | operand);        /*   and the control byte + EOI */
                imb_io_write (channel, Reg_0, PHI_SDC);                 /*   and Selected Device Clear */
                imb_io_write (channel, Reg_0, PHI_UNLISTEN);            /*   and Unlisten */
                }
            break;


        case Read_Modify_Write:                         /* Read/Modify/Write */
            reg = CPI_REGISTER (instruction);           /* get the register number to modify */
            mask = HP_BIT (CPI_BIT_NUMBER (operand));   /*   and form the bit mask */

            response = imb_io_read (channel, reg);      /* read the register value */

            if (operand & CPI_SET_BIT)                          /* if the bit is being set */
                imb_io_write (channel, reg, response | mask);   /*   then merge the target bit */
            else                                                /* otherwise */
                imb_io_write (channel, reg, response & ~mask);  /*   clear the target bit */
            break;


        case Read_Register:                                                 /* Read Register */
            response = imb_io_read (channel, CPI_REGISTER (instruction));   /* read the register value */
            mem_write (&cpp_dev, bank_0, drt0 + 1, response);               /*   and save it as the operand */
            break;


        case Write_Register:                                    /* Write Register */
            imb_io_write (channel, CPI_REGISTER (instruction),  /* write the operand */
                          operand);                             /*   to the target register */
            break;


        case Command_HPIB:                              /* Command HP-IB */
            response = imb_io_read (channel, Reg_2);    /* read the PHI conditions */

            if (not (response & R2_EMPTY)) {            /* if the outbound FIFO is not empty */
                suspend_program (channel, device,       /*   then suspend the program */
                                 drt, response);        /*     until the all bytes are sent */
                executing = FALSE;
                }

            else {                                      /* otherwise */
                max = CPI_CMD_COUNT (instruction);      /*   get the count of bytes to write */

                if (max == 0)                           /* if the count value is zero */
                    max = 8;                            /*   then write eight bytes */

                data = operand;                         /* the first two bytes are in the operand word */
                count = 1;                              /*   and start with the first byte */

                do {                                    /* loop through the list of commands */
                    if (count & 1)                      /* if the count is odd */
                        cmd = UPPER_BYTE (data);        /*   then get the upper byte */
                    else                                /* otherwise */
                        cmd = LOWER_BYTE (data);        /*   get the lower byte */

                    imb_io_write (channel, Reg_0,           /* send the command to the bus */
                                  R0_MODE_COMMAND | cmd);   /*   and assert ATN with the byte */

                    count++;                            /* increment the count of commands */

                    if (count > max)                    /* if all commands have been sent */
                        break;                          /*   then quit */

                    else if (count & 1)                 /* otherwise if a new command word is needed */
                        mem_read (&cpp_dev, bank_0,     /*   then read it */
                                  drt0 + 1 + count / 2,
                                  &data);
                    }
                while (TRUE);                           /* continue until all commands are sent */
                }
            break;


        case Execute_DMA:                                   /* Execute DMA */
            mem_read (&cpp_dev, bank_0, drt0 + 2, &burst);  /* get the burst word */
            mem_read (&cpp_dev, bank_0, drt0 + 3, &flags);  /*   and the flags word */

            burst = CPI_BURST_LEN (burst);              /* get the burst length */

            if (burst == 0)                             /* if the burst length is zero */
                burst = 256;                            /*   then reset to 256 bytes */

            if (operand == 0) {                         /* if the byte count is zero */
                abort_program (channel, device,         /*   then abort the program */
                               drt, CPA_CHAIN);         /*     with a data chain error */
                executing = FALSE;
                }

            else {                                      /* otherwise */
                if (flags & CPI_BURST_MODE              /*   if in burst mode and the burst */
                  && burst < operand)                   /*     is smaller than the byte count */
                    operand = burst;                    /*       then transfer just the burst */

                if (flags & CPI_DIRECTION) {            /* if the direction is outbound */
                    if (flags & CPI_BURST_MODE)         /*   then if burst mode */
                        flags &= CPI_BURST_FLAGS;       /*     then mask to the burst flag set */
                    else                                /*   otherwise */
                        flags &= CPI_RECORD_FLAGS;      /*     mask to the record flag set */

                    response = imb_io_read (channel, Reg_6) /* mask the PHI configuration */
                                 & R6_FREEZE                /*   to just the parity freeze */
                                 | R6_REN | R6_OUTBOUND;    /*     and set REN and the DMA direction */

                    imb_io_write (channel, Reg_6, response);    /* rewrite the PHI configuration */

                    imb_io_write (channel, Reg_3, R3_ENABLE | R3_EMPTY);    /* unmask the empty interrupt */
                    }

                else {                                          /* otherwise the direction is inbound */
                    response = imb_io_read (channel, Reg_1);    /*   so read the PHI status register */

                    if (response & R1_CNTLR) {                          /* if the channel is the controller */
                        if (flags & CPI_BURST_MODE)                     /*   then if in burst mode */
                            if (flags & CPI_TERM_LF)                    /*     then if LF reception terminates */
                                count = R0_MODE_LF_COUNTED | operand;   /*       then receive with LF termination */
                            else                                        /*     otherwise */
                                count = R0_MODE_COUNTED | operand;      /*       receive the count */

                        else                                            /* otherwise it's a record mode transfer */
                            count = R0_MODE_UNCOUNTED;                  /*   so receive an uncounted transfer */

                        imb_io_write (channel, Reg_0, count);   /* enable reception */
                        }

                    flags &= CPI_READ_FLAGS;            /* mask to just the R and N bits */
                    }

                initiate_dma (channel, device, drt,     /* setup and start */
                              drt0, operand, flags);    /*   the DMA transfer */
                executing = FALSE;                      /*     and wait until it completes */
                }
            break;


        case Write_Relative_Immediate:                  /* Write Relative Immediate */
            mem_write (&cpp_dev, bank_0,                /* write the operand */
                       drt0 + 2 + SEXT8 (instruction),  /*   to the memory location */
                       operand);                        /*     displaced by the sign-extended value */
            break;


        case CRC_Initialize:                            /* CRC Initialize */
        case CRC_Compare:                               /* CRC Compare */
        case Invalid:                                   /* invalid instruction */
            abort_program (channel, device,             /* abort the program */
                           drt, CPA_INVALID);           /*   with an invalid instruction error */
            executing = FALSE;
            break;
        }

    if (executing) {                                    /* if execution continues */
        drt0 += word_count [opcode];                    /*   then point at the next instruction */

        execution_count--;                              /* drop the execution count */

        if (execution_count == 0) {                     /* if the execution limit is exhausted */
            execution_limit_set |= BIT (channel);       /*   then set the channel */
            restart_set [channel] |= BIT (device);      /*      and device limit bits */

            executing = FALSE;                          /* stop execution */
            }
        }
    }                                                   /* end while */

mem_write (&cpp_dev, absolute, drt + 0, drt0);          /* update the channel program pointer */

return;                                                 /* return with execution complete or suspended */
}


/* Initiate a DMA transfer.

   This routine is called to configure and start a DMA transfer.  On entry, the
   "channel" parameter contains the channel number, "device" contains the device
   number on the channel, "drt" contains the address of the DRT entry, "drt0"
   contains the channel program pointer (from DRT word 0), "operand" contains
   the operand word of the DMA channel instruction, and "flags" contains the
   flags word of the channel instruction.

   The routine configures the DMA starting address and byte count, sets the DMA
   Wait bit in the channel status word, configures the DMA options, and starts
   the transfer.
*/

static void initiate_dma (uint32 channel, uint32  device,  uint32  drt,
                          uint32 drt0,    HP_WORD operand, HP_WORD flags)
{
HP_WORD address, dma, drt3;

imb_io_write (channel, Reg_8, CPI_BANK (flags));        /* set the DMA upper address */

mem_read (&cpp_dev, bank_0, drt0 + 4, &address);        /* read the address word from the channel instruction */
imb_io_write (channel, Reg_9, CPI_OFFSET (address));    /*   and set the DMA lower address */

imb_io_write (channel, Reg_A, CPI_COUNT (operand));     /* set the DMA byte count */

mem_read (&cpp_dev, absolute, drt + 3, &drt3);          /* get the status word */
drt3 = drt3 & CPS_STATE_MASK | CPS_DMAWAIT;             /*   and set the DMA wait bit */
mem_write (&cpp_dev, absolute, drt + 3, drt3);          /*     and update the DRT */

dma = RB_TO_DMA_FLAGS (CPI_DMA_FLAGS (flags));          /* get the B/R/E/D flags */

if (flags & CPI_NO_INCREMENT)                           /* if address increment is inhibited */
    dma |= RB_NO_INCREMENT;                             /*   then disable the address counter */

imb_io_write (channel, Reg_B, dma | device);            /* configure and start the DMA transfer */

return;                                                 /* return with the DMA transfer initiated */
}


/* Complete a DMA transfer.

   This routine is called to finish a DMA transfer when CSRQ is asserted and the
   program status word indicates a DMA wait.  On entry, the "channel" parameter
   contains the channel number, "device" contains the device number on the
   channel, and "drt" contains the address of the DRT entry.

   Completion takes one of three actions, depending on the program state.

   If the transfer is not chained, or if it is the last transfer in a chain,
   then the transaction is ended, and channel program execution continues with
   the next instruction.  The location of the next instruction is determined by
   the manner of termination, e.g., whether or not EOI was received or the byte
   count was exhausted, whether burst or record mode is active, etc.

   If the transfer is chained and is not the last transfer, then the transaction
   continues.  DMA is reinitialized for the next transfer in the chain, and
   program execution suspends until the new transfer completes.

   If the transfer ended with a DMA error, e.g., an address overflow or a
   chained operation error, then program execution is aborted.  In this case,
   the address at which the abort occurred is written to CPVA locations 4 and 5,
   and the DMA status indicating the reason for the abort is written to CPVA
   location 0.

   Completion is complicated by different handling of record vs. burst mode
   transfers, chained vs. unchained transfers, and byte count vs. EOI
   termination, all of which factor into the determination of the location of
   the next instruction to execute.  Where '*' represents the first location
   following the Read, Read Control, or Execute DMA (Read) instruction,
   termination transfers to:

     Location  Reason for Read Termination
     --------  -----------------------------------------
      * + 0    End of transaction on EOI receipt
      * + 2    End of burst transfer but not transaction
      * + TD   End of transaction on Byte Count

   For the Write, Write Control, or Execute DMA (Write), termination transfers
   to:

     Location  Reason for Write Termination
     --------  -----------------------------------------
     * + 0     End of transaction
     * + 2     End of burst transfer but not transaction

   Data chaining in particular employs an optimization to skip over chained
   instructions that have completed their transfers.  Consider this
   four-instruction burst-mode transfer:

     RB 0,AA(0),100,50,DC=3
     RB 0,BB(0),100,50,DC=2
     RB 0,CC(0),100,50,DC=1
     RB 0,DD(0),100,50,DC=0
     JUMP <done>
     WAIT
     JUMP <RB>

   The transaction begins with the channel program pointer at the first
   instruction.  The first 50-byte transfer is made, the byte count and address
   are updated, and then the DC field is used to move the program pointer
   directly to the WAIT instruction.  After the JUMP moves the pointer back to
   the first RB instruction, the second 50-byte transfer is made, and the byte
   count and address are updated.

   At this point, the first RB instruction is complete.  When the JUMP is
   executed to return the pointer to the first instruction, the CPP would have
   to read the byte count, discover that it is zero, and then increment the
   pointer to execute the second RB instruction.  As each instruction completes
   its two bursts, the process would repeatedly read-and-skip over the set of
   completed instructions before reaching the remaining active one(s).

   To eliminate these unnecessary program instruction reads, completion of the
   first instruction's bursts sets the DC field to 1, so that the field
   indicates the number of instructions to skip to reach the next active
   instruction.  Completion of each succeeding instruction's bursts increments
   the first instruction's DC field.  This allows the CPP to move directly from
   the first instruction (target of the JUMP) to the active instruction without
   having to examine the intermediate instructions for non-zero byte counts.  In
   the example, updating of the first instruction's DC field proceeds as
   follows:

     RB DC=3  -->  RB DC=1 --+  -->  RB DC=2 --+  -->  RB DC=3 --+
     RB DC=2       RB DC=2 <-+       RB DC=2   |       RB DC=2   |
     RB DC=1       RB DC=1           RB DC=1 <-+       RB DC=1   |
     RB DC=0       RB DC=0           RB DC=0           RB DC=0 <-+

   Interpretation of the DC field occurs when the first instruction is
   re-executed and its byte count is zero.

   Note that the program pointer remains at the first instruction when
   subsequent chained instructions are executing, rather than being updated to
   point at the current instruction.  So when a DMA completion request causes
   the CPP to be reentered, the first instruction's DC field is again used to
   locate the current instruction.  This is necessary to be able to increment
   the first instruction's DC field when a subsequent instruction's byte count
   is exhausted.

   Note also that when all instructions have executed, the original DC field
   value is automatically restored to the first instruction.  This does not
   occur, however, if the transaction terminates abnormally, e.g., by receiving
   a byte with EOI before the count is exhausted.  For example, if the second
   instruction receives an EOI, the first instruction's DC field will remain set
   to 1 instead of being restored to 3.  This seems like a bug, as the CPP could
   restore the value by adding the active instruction's DC field to the first
   instruction's DC field and rewriting the latter.


   Implementation notes:

    1. In burst mode, ending with DMA count done is an error because PHI and DMA
       were both set up to end on the same count.  Ending with PHI count done
       takes precedence in reporting, so if DMA ends with its count, then DMA
       counted more bytes than the PHI did.

    2. DMA writes end either with an error or with byte count exhaustion.  Note
       that byte count completion status for writes is 0, while for reads it is
       2.

    3. It is not necessary to mask offsets from the channel program pointer
       before calling execute_program as that routine masks the parameter before
       use.
*/

static void complete_dma (uint32 channel, uint32 device, uint32 drt)
{
CP_OPCODE opcode;
HP_WORD   instruction, operand, flags, term_burst, burst, drt0;
IMB_DATA  response, dma_status;
uint32    chain, end_status;

mem_read (&cpp_dev, absolute, drt + 0, &drt0);          /* read the channel program pointer */

mem_read (&cpp_dev, bank_0, drt0 + 0, &instruction);    /* read the current instruction */
mem_read (&cpp_dev, bank_0, drt0 + 1, &operand);        /*   and its operand */

opcode = CPI_OPCODE (instruction);                      /* get the opcode and the chain number */
chain = CPI_DATA_CHAIN (instruction);                   /*   from the instruction word */

imb_io_write (channel, Reg_3, R3_UNMASK);               /* unmask all PHI interrupts */

if (opcode == Read || opcode == Read_Control            /* if the current instruction */
  || opcode == Write || opcode == Write_Control         /*   is one of the DMA instructions */
  || opcode == Execute_DMA) {                           /*     then process DMA completion */

    if (operand == 0)                                   /* if the byte count is zero */
        if (chain == 0) {                               /*   then if the transfer is not chained */
            abort_program (channel, device,             /*     then abort the program */
                           drt, CPA_CHAIN);             /*       with a data chain error */
            return;
            }

        else {                                          /* otherwise */
            drt0 = drt0 + chain * 5;                    /*   point at the current chain entry */

            mem_read (&cpp_dev, bank_0, drt0 + 1, &operand);    /* get the chain entry byte count */

            if (operand == 0) {                         /* if the byte count is zero */
                abort_program (channel, device,         /*   then abort the program */
                               drt, CPA_CHAIN);         /*     with a data chain error */
                return;
                }

            else {                                      /* otherwise */
                mem_read (&cpp_dev, bank_0, drt0 + 0,   /*   get the chain entry instruction word */
                          &instruction);
                chain = CPI_DATA_CHAIN (instruction);   /*     and the new chain number */
                }
            }

    mem_read (&cpp_dev, bank_0, drt0 + 2, &term_burst); /* get the burst word */
    mem_read (&cpp_dev, bank_0, drt0 + 3, &flags);      /*   and the flags word */

    burst = CPI_BURST_LEN (term_burst);                 /* get the burst length */

    if (burst == 0)                                     /* if the burst length is zero */
        burst = 256;                                    /*   then reset it to 256 bytes */

    if (flags & CPI_BURST_MODE                          /* if this is a burst-mode transfer */
      && operand > burst)                               /*   and there are more bytes to transfer */
        operand = operand - burst;                      /*     then reduce the remainder by the burst length */
    else                                                /* otherwise */
        operand = 0;                                    /*   the transfer is complete */

    operand += imb_io_read (channel, Reg_A);            /* add any DMA residue to the byte count */

    dma_status = imb_io_read (channel, Reg_B);          /* get the DMA status word */
    end_status = RB_STATUS (dma_status);                /*   and the termination status */

    if (not (flags & CPI_NO_UPDATE)) {                      /* if updates are not suppressed */
        mem_write (&cpp_dev, bank_0, drt0 + 1, operand);    /*   then update the byte count */

        response = imb_io_read (channel, Reg_9);            /* get the ending DMA address */
        mem_write (&cpp_dev, bank_0, drt0 + 4, response);   /*   and update the channel instruction */

        if (dma_status & RB_RIGHT_BYTE)                 /* if the transfer ended with the right byte */
            flags |= CPI_RIGHT_BYTE;                    /*   then set the R bit for the next transfer */
        else                                            /* otherwise */
            flags &= ~CPI_RIGHT_BYTE;                   /*   clear the R bit */

        mem_write (&cpp_dev, bank_0, drt0 + 3, flags);  /* update the flags word in the instruction */
        }


    if (opcode == Read || opcode == Read_Control)       /* if this is an inbound transfer */
        if (flags & CPI_BURST_MODE) {                   /*   then if this is a burst mode transfer */
            clear_fifos (channel);                      /*     then clear the PHI FIFOs */

            if (end_status == RB_STATUS_RCOUNT          /* if DMA ended on the byte count */
              || end_status == RB_STATUS_ERROR)         /*   or due to an error */
                abort_program (channel, device, drt,    /*   then abort the program */
                               TO_CPA_DMA_ABORT (dma_status));

            else {                                      /* otherwise it ended on count or EOI */
                if (end_status == RB_STATUS_FINAL)      /*   so if the transfer ended on byte count */
                    if (operand > 0)                    /*     then if the transfer is not finished */
                        drt0 += 2;                      /*       then offset to (* + 2) */

                    else if (chain > 0)                             /* otherwise if chaining */
                        if (update_chain (drt, drt0, instruction))  /*   then if the chain update succeeds */
                            drt0 = drt0 + 2;                        /*     then offset to (* + 2) */

                        else {                                      /*   otherwise */
                            abort_program (channel, device,         /*     abort the program */
                                           drt, CPA_CHAIN);         /*       with a data chain error */
                            return;
                            }

                    else                                    /* otherwise an unchained transfer completed */
                        drt0 += CPI_TERM_DISP (term_burst); /*   so offset to (* + TD) */

                drt0 += (chain + 1) * word_count [opcode];      /* point at the next instruction to execute */
                execute_program (channel, device, drt, drt0);   /*   and resume program execution */
                }
            }

        else if (end_status == RB_STATUS_TAGGED) {          /* otherwise if record-mode DMA ended with EOI */
            imb_io_write (channel, Reg_0, PHI_UNTALK);      /*   then send Untalk */
            imb_io_write (channel, Reg_0, PHI_UNLISTEN);    /*     and Unlisten */

            drt0 += word_count [opcode];                    /* point at the next instruction */
            execute_program (channel, device, drt, drt0);   /*   and resume program execution */
            }

        else if (end_status == RB_STATUS_ERROR)         /* otherwise if record-mode DMA ended with an error */
            abort_program (channel, device, drt,        /*   then abort the program */
                           TO_CPA_DMA_ABORT (dma_status));

        else if (chain == 0) {                          /* otherwise if this is the last or only chain block */
            if (end_status == RB_STATUS_RCOUNT)         /*   then if DMA ended with byte count expiration */
                clear_fifos (channel);                  /*     then clear the PHI FIFOs */

            drt0 += CPI_TERM_DISP (term_burst)          /* point at (* + TD) */
                      + word_count [opcode];

            execute_program (channel, device, drt, drt0);   /* resume program execution */
            }

        else {                                                  /* otherwise a chained transfer ended */
            if (not update_chain (drt, drt0, instruction)) {    /*   so if the chain update fails */
                abort_program (channel, device,                 /*     then abort the program */
                               drt, CPA_CHAIN);                 /*       with a data chain error */
                return;
                }

            drt0 += word_count [opcode];                        /* point at the next chain entry */
            mem_read (&cpp_dev, bank_0, drt0 + 1, &operand);    /*   and get the chain entry byte count */

            if (operand == 0) {                         /* if the byte count is zero */
                abort_program (channel, device,         /*   then abort the program */
                               drt, CPA_CHAIN);         /*     with a data chain error */
                return;
                }

            else {                                                      /* otherwise */
                mem_read (&cpp_dev, bank_0, drt0 + 3, &flags);          /*   get the new flags word */
                mem_read (&cpp_dev, bank_0, drt0 + 0, &instruction);    /*     and the new instruction word */
                }

            if (TRACING (cpp_dev, TRACE_CMD))
                trace_instruction (channel, instruction, operand, drt0);

            initiate_dma (channel, device, drt, drt0,   /* configure and start DMA */
                          operand, flags);              /*   for the next transfer in the chain */
            }


    else if (opcode == Write || opcode == Write_Control)        /* otherwise if this is an outbound transfer */
        if (flags & CPI_BURST_MODE) {                           /*   then if this is a burst-mode transfer */
            if (operand == 0 && chain > 0                       /*     then if the current chain is complete */
              && not update_chain (drt, drt0, instruction)) {   /*     and the chain update fails */
                abort_program (channel, device,                 /*       then abort the program */
                               drt, CPA_CHAIN);                 /*         with a data chain error */
                return;
                }

            imb_io_write (channel, Reg_6, R6_FREEZE | R6_REN);  /* enable parity freeze and assert REN */
            imb_io_write (channel, Reg_0, PHI_UNLISTEN);        /*   and Unlisten the device */

            drt0 += (chain + 1) * word_count [opcode];  /* point at the next instruction (* + 0) */

            if (operand > 0 || chain > 0)               /* if the transaction is incomplete */
                drt0 += 2;                              /*   then skip the next instruction (* + 2) */

            execute_program (channel, device, drt, drt0);   /* resume program execution */
            }

        else if (chain == 0) {                                  /* otherwise if this is the last or only chain block */
            imb_io_write (channel, Reg_6, R6_FREEZE | R6_REN);  /*   then enable parity freeze and assert REN */
            imb_io_write (channel, Reg_0, PHI_UNLISTEN);        /*     and Unlisten the device */

            drt0 += word_count [opcode];                    /* point at the next instruction */
            execute_program (channel, device, drt, drt0);   /*   and resume program execution */
            }

        else {                                                  /* otherwise it's an intermediate chain */
            if (not update_chain (drt, drt0, instruction)) {    /*   so if the chain update fails */
                abort_program (channel, device,                 /*   then abort the program */
                               drt, CPA_CHAIN);                 /*     with a data chain error */
                return;
                }

            drt0 += word_count [opcode];                        /* point at the next chain entry */
            mem_read (&cpp_dev, bank_0, drt0 + 1, &operand);    /*   and get the chain entry byte count */

            if (operand == 0) {                         /* if the byte count is zero */
                abort_program (channel, device,         /*   then abort the program */
                               drt, CPA_CHAIN);         /*     with a data chain error */
                return;
                }

            else {                                                      /* otherwise */
                mem_read (&cpp_dev, bank_0, drt0 + 3, &flags);          /*   get the new flags word */
                mem_read (&cpp_dev, bank_0, drt0 + 0, &instruction);    /*     and the new instruction word */
                chain = CPI_DATA_CHAIN (instruction);                   /*       and the new chain number */
                }

            flags |= CPI_DIRECTION;                     /* set the outbound direction */

            if (chain == 0)                             /* if this is the last or only transfer */
                flags &= ~CPI_NO_EOI;                   /*   then tag it with EOI */
            else                                        /* otherwise */
                flags |= CPI_NO_EOI;                    /*   suppress the EOI tag */

            response = imb_io_read (channel, Reg_6)     /* mask the PHI configuration */
                         & R6_FREEZE                    /*   to just the parity freeze */
                         | R6_REN | R6_OUTBOUND;        /*     and set REN and the DMA direction */

            imb_io_write (channel, Reg_6, response);    /* rewrite the PHI configuration */

            imb_io_write (channel, Reg_3, R3_ENABLE | R3_EMPTY);    /* unmask the empty interrupt */

            if (TRACING (cpp_dev, TRACE_CMD))
                trace_instruction (channel, instruction, operand, drt0);

            initiate_dma (channel, device, drt, drt0,   /* configure and start DMA */
                          operand, flags);              /*   for the next transfer in the chain */
            }

    else {                                              /* otherwise complete the Execute DMA instruction */
        response = imb_io_read (channel, Reg_6)         /*   so mask the PHI configuration */
                     & (R6_FREEZE | R6_REN)             /*   to just the parity freeze and REN state */
                     | R6_CLEAR;                        /*     and clear the outbound FIFO */

        imb_io_write (channel, Reg_6, response);        /* rewrite the PHI configuration */

        drt0 += word_count [opcode];                    /* point at the next instruction (* + 0) */

        if (RB_STATUS (dma_status) == RB_STATUS_ERROR) {    /* if DMA ended with an error */
            abort_program (channel, device, drt,            /*   then abort the program */
                           TO_CPA_DMA_ABORT (dma_status));
            return;
            }

        else if (flags & CPI_BURST_MODE && operand > 0      /* otherwise if an incomplete burst-mode */
          && (flags & CPI_DIRECTION                         /*   outbound transfer */
          || RB_STATUS (dma_status) != RB_STATUS_TAGGED))   /*   or inbound transfer ended on byte count */
            drt0 += 2;                                      /*   then skip the next instruction (* + 2) */

        else if (not (flags & CPI_DIRECTION)                /* otherwise if an inbound transfer */
          && RB_STATUS (dma_status) != RB_STATUS_TAGGED)    /*   ended on byte count */
            drt0 += CPI_TERM_DISP (term_burst);             /*     then jump to the instruction at (* + TD) */

        execute_program (channel, device, drt, drt0);   /* resume program execution */
        }
    }

else                                                    /* otherwise */
    abort_program (channel, device,                     /*   abort the program because completion */
                   drt, CPA_INVALID);                   /*     was not for a DMA instruction */

return;
}


/* Update the chain number.

   This routine is called to update the chain number of the first instruction in
   a sequence of chained Read or Write instructions.  On entry, the "drt"
   "drt" parameter contains the address of the DRT entry, "drt0" contains the
   updated channel program pointer, and "instruction" contains the first
   instruction in the chain.  The routine returns TRUE if the update succeeds
   and FALSE otherwise.

   Execution of a sequence of chained instructions is optimized by using the
   chain field of the first instruction as an index into the sequence.  Before
   execution begins, it is set to the number of chain entries that follow the
   initial entry.  When the initial instruction executes, its chain field is set
   to 1.  As each of the subsequent chain entry transfers is completed, the
   initial chain field is incremented.  During execution, then, the initial
   chain number indicates the number of entries to skip to get to the current
   chain entry.  This routine is called to perform this update.

   For the initial call, the value in memory location "drt" + 0 is the same as
   the value passed in "drt0".  For subsequent calls, "drt0" has been updated to
   point at the current chain entry, while "drt" + 0 remains pointing at the
   initial entry, i.e., the head of the chain.

   Note that when all of the transfers have completed, the original chain field
   value will have been automatically restored to the initial instruction.
*/

static t_bool update_chain (uint32 drt, HP_WORD drt0, HP_WORD instruction)
{
HP_WORD head;

mem_read (&cpp_dev, absolute, drt + 0, &head);          /* get the pointer to the head of the chain */

if (drt0 == head)                                       /* if this is the initial call */
    instruction = instruction & ~CPI_CHAIN_MASK         /*   then set the chain field to 1 */
                 | CPI_TO_CHAIN (1);

else if (drt0 > head) {                                 /* otherwise if it is a subsequent call */
    mem_read (&cpp_dev, bank_0, head, &instruction);    /*  then get the initial instruction */
    instruction = instruction + CPI_CHAIN_LSB;          /*    and increment the chain field */

    if (CPI_DATA_CHAIN (instruction) == 0)              /* if the chain increment overflowed */
        return FALSE;                                   /*   then return failure to the caller */
    }

else                                                    /* otherwise */
    return FALSE;                                       /*   a chain error has occurred */

mem_write (&cpp_dev, bank_0, head, instruction);        /* rewrite the head of the chain */
return TRUE;                                            /*   and return success */
}


/* Clear the PHI FIFOs.

   This routine clears the outbound and inbound FIFOs in the PHI.  On entry,
   "channel" is the channel number of the GIC containing the PHI to clear.

   The PHI provides a control bit to clear the outbound FIFO.  There is no
   corresponding control bit to clear the inbound FIFO.  For the latter, we must
   read bytes out of the FIFO until the FIFO Empty status bit is set.

   If the outbound FIFO is already empty on entry, the bus is idled by
   unaddressing all devices.  If data remains in the FIFO, an Interface Clear is
   done under the assumption that a device may be partially addressed, i.e.,
   stuck in some intermediate state.  The IFC will unaddress all devices and
   clear their HP-IB interfaces.
*/

static void clear_fifos (uint32 channel)
{
IMB_DATA response;

response = imb_io_read (channel, Reg_2);                /* read the interrupt conditions */

if (response & R2_EMPTY) {                              /* if the outbound FIFO is empty */
    imb_io_write (channel, Reg_0, PHI_UNTALK);          /*   then send Untalk */
    imb_io_write (channel, Reg_0, PHI_UNLISTEN);        /*     and Unlisten */

    imb_io_write (channel, Reg_6, R6_FREEZE | R6_REN);  /* enable parity freeze and assert REN */
    }

else {                                                  /* otherwise the FIFO is occupied */
    imb_io_write (channel, Reg_6, R6_IFC);              /*   so assert IFC and then deny IFC */
    imb_io_write (channel, Reg_6, R6_REN | R6_CLEAR);   /*     and clear the FIFO  */
    }

if (response & R2_DATA)                                 /* if the inbound FIFO is occupied */
    do                                                  /*   then */
        imb_io_read (channel, Reg_0);                   /*     read and toss the data */
    while (imb_io_read (channel, Reg_2) & R2_DATA);     /*       until all data has been removed */

return;
}


/* Suspend to wait for data.

   This routine suspends the program until data has been received from the
   HP-IB.  On entry, the "channel" parameter contains the channel number,
   "device" contains the device number on the channel, and "drt" contains the
   address of the DRT entry.

   The routine unmasks the Data Available interrupt, disables parallel poll
   recognition, and sets the "waiting for data" bit in the channel status word.
   When data is received by the PHI, it will interrupt, and the suspended
   program will resume with data in the inbound FIFO.
*/

static void suspend_for_data (uint32 channel, uint32 device, uint32 drt)
{
HP_WORD drt3;

imb_io_write (channel, Reg_3, R3_ENABLE | R3_DATA);     /* unmask the inbound FIFO data interrupt */
imb_io_write (channel, Reg_F, RF_NO_POLL | device);     /*   and disable poll recognition */

mem_read (&cpp_dev, absolute, drt + 3, &drt3);          /* get the status word */
drt3 = drt3 & CPS_STATE_MASK | CPS_STATUSWAIT;          /*   set the "waiting for FIFO data" bit */
mem_write (&cpp_dev, absolute, drt + 3, drt3);          /*     and write it back */

return;
}


/* Service the IMB Adapter.

   This service routine is called to signal completion of an IMBA command.
   After processing an IMBA command, the completion status is stored in the UNIT
   structure, and unit service is scheduled.  The delay represents the time
   taken to complete the command.

   Completion is indicated by storing the status into mailbox location 4.


   Implementation notes:

    1. The UNIT wait field is set when the unit is scheduled and reset when the
       event is serviced.  This allows schedule calls to be overlapped.  See the
       note in the activate_unit routine for details.
*/

static t_stat imba_service (UNIT *uptr)
{
uptr->wait = -1;                                        /* reset the activation time */

tprintf (cpp_dev, TRACE_SERV, "IMBA completion service entered\n");

mem_write (&cpp_dev, absolute, MBX4, Mailbox_Status);   /* write the completion status to memory */

return SCPE_OK;
}


/* Service a cold load timeout.

   This service routine is called to signal that the channel used for cold
   loading did not respond within a reasonable amount of time.  An upper time
   limit is placed on cold load responses to ensure that the CPU does not hang
   in the cold load microcode while waiting for a down or missing device.

   Entry causes the cold load state to be changed to service the timeout, and
   a command is sent to the IMBA to interrupt the CPU.  The latter causes
   reentry into the cold load routine, where the change of state leads to a
   system halt.


   Implementation notes:

    1. The UNIT wait field is set when the unit is scheduled and reset when the
       event is serviced.  This allows schedule calls to be overlapped.  See the
       note in the activate_unit routine for details.
*/

static t_stat timeout_service (UNIT *uptr)
{
uptr->wait = -1;                                        /* reset the activation time */

tprintf (cpp_dev, TRACE_SERV, "Cold load timeout service entered\n");

Loader_State = CL_Timeout;                              /* move to the timeout state */
imb_io_write (IMBA_CHANNEL, 0, Loader_Device);          /*   and tell the IMBA to interrupt */

return SCPE_OK;
}



/* Interface local utility routines */



/* Reset the channel program processor.

   This routine is called for a RESET or RESET CPP command.  It is the
   simulation equivalent of the IORESET signal, which is asserted by the front
   panel LOAD and DUMP switches.

   CPP reset simply cancels any event services that are currently active.
*/

static t_stat cpp_reset (DEVICE *dptr)
{
sim_cancel (&Completion_Unit);                          /* cancel any active completion event */
Completion_Unit.wait = -1;                              /*   and reset the activation time */

sim_cancel (&Timeout_Unit);                             /* cancel any active timeout event */
Timeout_Unit.wait = -1;                                 /*   and reset the activation time */

return SCPE_OK;                                         /* the routine always succeeds */
}


/* Activate a unit.

   This routine is called to schedule an event service.  On entry, "uptr" points
   at the unit to schedule, and "delay" is the service delay.

   If the specified unit is already activated, the remaining delay before
   servicing is obtained.  If the new delay is shorter, then the unit is
   rescheduled for the shorter time.  If the new delay is longer, the existing
   schedule is maintained.  The effect is that event service will occur no later
   than the earlier activation requested.

   To permit this, the unit's "wait" field is used to store the current delay
   when the unit is activated and is reset when the unit is serviced.  This
   allows the unit to be checked for current activation without calling the
   sim_is_active routine, which does a linear scan of all scheduled events.
*/

static t_stat activate_unit (UNIT *uptr, int32 delay)
{
int32  remaining;
t_stat result;

if (uptr->wait >= 0) {                                  /* if the unit is currently scheduled */
    remaining = sim_is_active (uptr) - 1;               /*   then get the remaining delay */

    if (delay >= remaining)                             /* if the new delay is longer */
        return SCPE_OK;                                 /*   then let the current delay expire */
    else                                                /* otherwise */
        sim_cancel (uptr);                              /*   cancel the remaining time to reschedule */
    }

tprintf (cpp_dev, TRACE_SERV, "%s delay %d service scheduled\n",
         unit_names [uptr - cpp_unit], delay);

result = sim_activate (uptr, delay);                    /* activate the unit for the requested delay */
uptr->wait = delay;                                     /*   and mark the unit as currently scheduled */

return result;                                          /* return the activation status */
}


/* Format and print a channel program instruction.

   This routine is called from fprint_sym to print a channel instruction in
   symbolic format.  It is also called from trace_instruction to trace
   instruction execution and from trace_program to trace an entire program.

   On entry, "ofile" is the output stream on which to write the symbolic
   interpretation, "val" points at a two-word array containing the first two
   words of the instruction, "radix" is the specified radix override for numeric
   output or zero if no radix was specified, "addr" is the memory or device
   address of the first instruction word, and "uptr" points at the unit from
   which the instructions are read or is NULL if the source is a register or
   main memory.  The routine returns the negative number of "extra" (i.e.,
   beyond the first) addressing units consumed by the instruction if formatting
   was successful, or an SCPE error code if a failure occurred.

   While each channel instruction is a set of 16-bit words (typically two, but
   sometimes larger), the source may be word-oriented, such as main memory, or
   byte-oriented, such as a disc or tape drive.  The "addr" parameter will
   therefore be either a byte or word address, and the return value must use the
   same convention.

   The routine begins by obtaining a device pointer from the supplied unit
   pointer.  If the unit pointer is NULL, the default device (CPU) is used.
   Otherwise a lookup is done to obtain the device pointer.  These pointers are
   cached, so that successive calls for the same unit, such as are done when
   printing the entire channel program, need not repeat the lookup.

   A local buffer is used to hold the entire instruction, so that additional
   instruction words need not be read while interleaving the output.  The first
   VM_EMAX words of the instruction have been read into the "val" array prior to
   the call.  The opcode portion of the first word is used to look up the number
   of words needed.  Then the additional words needed, if any, are read and
   stored in the buffer.

   Once all words needed are in the buffer, the instruction name is output, and
   then the opcode is used to dispatch to the instruction-specific formatter.
   The Device Specified Jump and Command HP-IB instructions have a variable
   number of operands, so they are output in a loop.  The other instructions are
   output directly.

   The Relative Jump and Device Specified Jump instructions use signed 16-bit
   displacements.  If a backward displacement is larger than the instruction
   address, then an address wraparound will occur.  This would be an error if
   the instructions were in memory but not necessarily an error if they were
   being read from a device.  This is because the displacement will be relative
   to the eventual location in memory and not relative to the device address,
   which is arbitrary.  For wraparounds, the routine prints the jump address as
   a relative displacement in the form "*-<n>" rather than as an absolute device
   address.

   Once the output is complete, the routine returns the number of words used as
   a negative number.  This value is used by the callers to advance the address
   when an instruction sequence is to be printed.


   Implementation notes:

    1. The word_count table gives the length of the instructions.  All
       instructions are fixed-length, except for the Device Specified Jump
       instruction that depends on the count of potential return values.  The
       DSJ entry therefore is set to three words, which is the minimum length;
       the actual length is 3 + N, where N is the count of return values
       obtained from the Maximum Response field of the first instruction word.

    2. The DSJ instruction can be up to 258 words long.  Because words are read
       in groups of VM_EMAX words at a time, the instruction buffer array is
       declared that much larger to ensure that a group read won't extend past
       the end of the array.

    3. If a radix override is not specified, the default data radix for the
       specified device is used in most cases.  Addresses are always printed in
       the device's address radix, and a few data values are printed using the
       conventional radix for the data (e.g., Amigo IDs are always printed in
       hexadecimal).

    4. The routine returns the number of extra words consumed even when a
       byte-oriented device is specified.  In such cases, our caller
       (fprint_sym) will adjust the returned result based on the address
       increment of the device.
*/

t_stat fprint_cpi (FILE *ofile, t_value *val, uint32 radix, t_addr addr, UNIT *uptr)
{
static DEVICE *dptr      = NULL;                /* last device pointer used */
static UNIT   *last_uptr = NULL;                /* last unit pointer used */
CP_OPCODE     opcode;
t_stat        status;
uint32        target, instruction [258 + VM_EMAX];
uint32        stride, counter, index, burst, words, aformat;
uint32        secondary = 0;

if (uptr == NULL) {                                     /* if this is a register or trace print */
    dptr = sim_dflt_dev;                                /*   then use the CPU */
    uptr = dptr->units;                                 /*     and its associated unit */
    }

else if (uptr != last_uptr) {                           /* otherwise it's an evaluation or address print */
    dptr = find_dev_from_unit (uptr);                   /*   so get the device pointer from the unit */

    if (dptr == NULL)                                   /* if the unit is not associated with a device */
        return SCPE_IERR;                               /*   then it cannot be printed */
    }

last_uptr = uptr;                                       /* save the last unit pointer used */

stride = dptr->aincr;                                   /* get the address stride */

if (radix == 0)                                         /* if the radix was not specified */
    radix = dptr->dradix;                               /*   then use the default data radix */

if (dptr->aradix == 10)                                 /* if the address radix is decimal */
    aformat = PV_LEFT;                                  /*   then left-justify the format */
else                                                    /* otherwise */
    aformat = PV_RZRO;                                  /*   right-justify and zero-fill the format */

for (index = 0; index < VM_EMAX; index++)               /* copy the initial instruction words */
    instruction [index] = (uint32) val [index];         /*   to the buffer */

opcode = CPI_OPCODE (instruction [0]);                  /* get the opcode from the first word */
words = word_count [opcode];                            /*   and the count of words in the instruction */

if (opcode == Device_Specified_Jump)                    /* if this is a DSJ instruction */
    words = words + CPI_RESPONSE (instruction [0]);     /*   then include the number of branches */

for (counter = VM_EMAX; counter < words; counter += VM_EMAX) {  /* read the remaining instruction words */
    status = get_aval (addr + counter * stride, dptr, uptr);    /*   obtaining "stride" words at a time */

    if (status == SCPE_OK)                                          /* if the read succeeded */
        for (index = 0; index < VM_EMAX; index++)                   /*   then copy the words */
            instruction [index + counter] = (uint32) val [index];   /*     to the buffer */
    else                                                            /* otherwise */
        return status;                                              /*   the instruction cannot be printed */
    }

addr = addr + words * stride;                           /* point at the instruction that follows */

fputs (instruction_names [opcode], ofile);              /* output the instruction name */

switch (opcode) {                                       /* dispatch the instruction opcode */

    case Relative_Jump:                                     /* Relative Jump */
        target = addr + instruction [1] * stride & LA_MASK; /* get the target address */

        if (target > addr && instruction [1] & D16_SIGN) {  /* if the address wrapped around */
            fputs (" *-", ofile);                           /*   then output the target as a relative address */
            fputs (fmt_value (NEG16 (instruction [1]) * stride & LA_MASK,
                              dptr->aradix, LA_WIDTH, aformat), ofile);
            }

        else {                                          /* otherwise */
            fputc (' ', ofile);                         /*   output the target as an absolute address */
            fputs (fmt_value (target, dptr->aradix,
                              LA_WIDTH, aformat), ofile);
            }
        break;


    case Interrupt:                                     /* Interrupt Halt/Continue */
        fprintf (ofile, "/%s %s | CPVA %u",
                 (instruction [0] & CPI_HALT ? "Halt" : "Run"), /* output the halt or run state */
                 fmt_value (CPI_CODE (instruction [1]),         /*   and the interrupt code */
                            radix, 12, PV_RZRO),
                 CPI_CPVA (instruction [0]));                   /* output the CPVA location */
        break;


    case Wait:                                          /* Wait */
        fprintf (ofile, " | %sresponse %s",
                 fmt_bitset (instruction [0], wait_format), /* output the wait bits */
                 fmt_value (CPI_CODE (instruction [1]),     /*   and the serial poll response */
                            radix, 12, PV_RZRO));
        break;


    case Read_Control:                                  /* Read Control */
        secondary = 16;

    /* fall through into the Read case */

    case Read:                                          /* Read */
        secondary += CPI_MODIFIER (instruction [0]);    /* get the secondary modifier */
        burst = CPI_BURST_LEN (instruction [2]);        /*   and the burst length */

        if (instruction [3] & CPI_BURST_MODE && burst == 0) /* if burst mode and the burst length is zero */
            burst = 256;                                    /*   then use a length of 256 instead */

        fprintf (ofile, " secondary %s count %u burst %u address %s chain %u termination %s | %s",
                 fmt_value (secondary, radix, 5, PV_RZRO),          /* output the secondary */
                 instruction [1], burst,                            /*   and the byte count and burst length */
                 fmt_value (TO_DWORD (CPI_BANK (instruction [3]),   /*     and the target address */
                                      CPI_OFFSET (instruction [4])),
                            sim_dflt_dev->aradix, PA_WIDTH, PV_RZRO),
                 CPI_DATA_CHAIN (instruction [0]),                  /* output the chain number */
                 fmt_value (addr + CPI_TERM_DISP (instruction [2]) * stride & LA_MASK,
                            dptr->aradix, LA_WIDTH, aformat),       /* output the termination displacement */
                 fmt_bitset (instruction [3], dma_read_format));    /*   and the option bits */
        break;


    case Write_Control:                                 /* Write Control */
        secondary = 16;

    /* fall through into the Write case */

    case Write:                                         /* Write */
        secondary += CPI_MODIFIER (instruction [0]);    /* get the secondary modifier */
        burst = CPI_BURST_LEN (instruction [2]);        /*   and the burst length */

        if (instruction [3] & CPI_BURST_MODE && burst == 0) /* if burst mode and the burst length is zero */
            burst = 256;                                    /*   then use a length of 256 instead */

        fprintf (ofile, " secondary %s count %u burst %u address %s chain %u | %s",
                 fmt_value (secondary, radix, 5, PV_RZRO),          /* output the secondary */
                 instruction [1], burst,                            /*   and the byte count and burst length */
                 fmt_value (TO_DWORD (CPI_BANK (instruction [3]),   /*     and the target address */
                                      CPI_OFFSET (instruction [4])),
                            sim_dflt_dev->aradix, PA_WIDTH, PV_RZRO),
                 CPI_DATA_CHAIN (instruction [0]),                  /* output the chain number */
                 fmt_bitset (instruction [3], dma_write_format));   /*   and the option bits */
        break;


    case Device_Specified_Jump:                         /* Device Specified Jump */
        counter = CPI_RESPONSE (instruction [0]) + 3;   /* get the number of instruction words */

        for (index = 2; index < counter; index++) {     /* loop through the jump displacements */
            if (index > 2)                              /* if this is an additional entry */
                fputc (',', ofile);                     /*   then add a comma separator */

            target = addr + instruction [index] * stride & LA_MASK; /* get the target address */

            if (target > addr && instruction [index] & D16_SIGN) {  /* if the address wrapped around */
                fputs (" *-", ofile);                               /*   then output the target as a relative address */
                fputs (fmt_value (NEG16 (instruction [index]) * stride & LA_MASK,
                                  dptr->aradix, LA_WIDTH, aformat), ofile);
                }

            else {                                      /* otherwise */
                fputc (' ', ofile);                     /*   output the target as an absolute address */
                fputs (fmt_value (target, dptr->aradix,
                                  LA_WIDTH, aformat), ofile);
                }
            }
        break;


    case Identify:                                      /* Amigo Identify */
        fprintf (ofile, " | response %s",
                 fmt_value (instruction [1], 16, DV_WIDTH, PV_RZRO));   /* output the ID value */
        break;


    case Clear:                                         /* Clear */
        fputc (' ', ofile);
        fputs (fmt_value (CPI_CONTROL (instruction [0]),    /* output the control byte */
                           radix, D8_WIDTH, PV_RZRO), ofile);
        break;


    case Read_Modify_Write:                             /* Read/Modify/Write */
        fprintf (ofile, " register %X bit %u %s",
                 CPI_REGISTER (instruction [0]),                        /* output the register number */
                 CPI_BIT_NUMBER (instruction [1]),                      /*   and bit number */
                 (instruction [1] & CPI_SET_BIT ? "set" : "clear"));    /*     and the operation bit */
        break;


    case Read_Register:                                 /* Read Register */
        fprintf (ofile, " %X | response %s",
                 CPI_REGISTER (instruction [0]),                            /* output the register number */
                 fmt_value (instruction [1], radix, DV_WIDTH, PV_RZRO));    /*   and the returned word */
        break;


    case Write_Register:                                /* Write Register */
        fprintf (ofile, " %X value %s",
                 CPI_REGISTER (instruction [0]),                            /* output the register number */
                 fmt_value (instruction [1], radix, DV_WIDTH, PV_RZRO));    /*   and the supplied word */
        break;


    case Command_HPIB:                                  /* Command HP-IB */
        counter = CPI_CMD_COUNT (instruction [0]);      /* get the command count */

        if (counter == 0)                               /* if the count is zero */
            counter = 8;                                /*   then use a count of eight */

        index = 1;                                      /* start with the first command word */

        do {                                            /* output the commands */
            if (index > 1)                              /* if this is an additional command */
                fputc (',', ofile);                     /*   then add a comma separator */

            if (counter > 1) {                                          /* if at least two commands remain */
                fprintf (ofile, " %s, %s",
                         fmt_value (UPPER_BYTE (instruction [index]),   /*   then output the upper byte command */
                                    radix, D8_WIDTH, PV_RZRO),
                         fmt_value (LOWER_BYTE (instruction [index]),   /*     and the lower byte command */
                                    radix, D8_WIDTH, PV_RZRO));

                counter = counter - 2;                  /* drop the remaining count */
                }

            else {                                                      /* otherwise only one command remains */
                fprintf (ofile, " %s",
                         fmt_value (UPPER_BYTE (instruction [index]),   /*   so output it from the upper byte */
                                    radix, D8_WIDTH, PV_RZRO));
                counter = 0;                            /* the count is exhausted */
                }

            index++;                                    /* bump the command word index */
            }
        while (counter > 0);                            /* continue until all commands are output */
        break;


    case Execute_DMA:                                   /* Execute DMA */
        burst = CPI_BURST_LEN (instruction [2]);        /* get the burst length */

        if (instruction [3] & CPI_BURST_MODE && burst == 0) /* if burst mode and the burst length is zero */
            burst = 256;                                    /*   then use a length of 256 instead */

        fprintf (ofile, " %s count %u burst %u address %s termination %s | %s",
                 (instruction [3] & CPI_DIRECTION ? "write" : "read"),  /* output the transfer direction */
                 instruction [1], burst,                                /*   and the byte count and burst length */
                 fmt_value (TO_DWORD (CPI_BANK (instruction [3]),       /*     and the memory address */
                                      CPI_OFFSET (instruction [4])),
                            sim_dflt_dev->aradix, PA_WIDTH, PV_RZRO),
                 fmt_value (addr + CPI_TERM_DISP (instruction [2]) * stride & LA_MASK,  /* output the termination */
                            dptr->aradix, LA_WIDTH, aformat),
                 fmt_bitset (instruction [3],                           /* output the option bits */
                             instruction [3] & CPI_DIRECTION ? dma_write_format : dma_read_format));
        break;


    case Write_Relative_Immediate:                      /* Write Relative Immediate */
        fprintf (ofile, " %s value %s",
                 fmt_value (addr + SEXT8 (LOWER_BYTE (instruction [0])) * stride & LA_MASK, /* output the target */
                            dptr->aradix, LA_WIDTH, aformat),
                 fmt_value (instruction [1], radix, DV_WIDTH, PV_RZRO));    /* output the data value */
        break;


    case CRC_Initialize:                                /* CRC Initialize */
        break;                                          /* this instruction has no options */


    case CRC_Compare:                                   /* CRC Compare */
        fprintf (ofile, "/%s %s%s | CPVA %u",
                 (instruction [0] & CPI_HALT ? "Halt" : "Run"), /* output the halt or run state */
                 fmt_value (CPI_CODE (instruction [1]),         /*   and the interrupt code */
                            radix, 12, PV_RZRO),
                 (instruction [0] & CPI_IRQ_MATCH ? " | interrupt on match" : ""),  /* output the interrupt bit */
                 CPI_CPVA (instruction [0]));                                       /*   and the CPVA location */
        break;


    case Invalid:                                       /* invalid instruction */
        break;
    }

return -(t_stat) (words - 1);                           /* return the number of words consumed */
}


/* Trace a channel program.

   This routine is called to trace a channel program in memory.  On entry, the
   "starting_address" parameter contains the address of the first instruction.
   The routine will follow the program flow and print all of the instructions.
   It assumes that the debug output device (sim_deb) is open and the OPND trace
   option is in effect when it is called.

   To print the entire program, the routine maintains a running "ending address"
   that corresponds to the last channel instruction of the current program.  For
   most instructions, the ending address is moved forward by the length of the
   instruction, so that printing continues.  However, halt and jump instructions
   may alter this.  For each instruction, a "target" address is set as follows:

     - A Relative Jump sets the target to the jump address.

     - A Device Specified Jump sets the target to the largest jump address.

     - A Write, Write Control, or Execute DMA (write) sets the target to P + 2,
       as that is a conditional target.

     - A Read, Read Control, or Execute DMA (read) sets the target to either P +
       2 or P + the termination displacement, as both are conditional targets.

     - All other instructions set the target to the current address plus the
       instruction length.

   Given an ending address that is initialized to the starting address, then for
   each instruction, if the target is greater than the current ending address,
   the ending address is set to the target address.  Printing continues until
   the current address is equal to the ending address or the current address
   points to an invalid instruction.  The idea is that all of the instructions
   in a channel program must be reachable, either through sequential execution,
   or as the target of a jump, and that an Interrupt/Halt instruction not
   followed by any jump targets must end the sequence.


   Implementation notes:

    1. The call to "fprint_cpi" can alter the "sim_eval" values, but only if the
       channel instruction uses more than two words.  As the Interrupt and
       Relative Jump instructions are each two words in length, "sim_eval" will
       be unchanged by the call and therefore valid for use.

    2. The Read [Control] and Execute DMA (read) instructions can return to
       location P + 0 (figured from the end of the instruction) or optionally to
       P + 2, or P + the termination displacement.  The Write [Control] and
       Execute DMA (write) instructions can return to location P + 0 or
       optionally to P + 2.  The farthest address is adjusted accordingly.

    3. The algorithm does not recognize data that is interspersed with
       instructions and preceded by Relative Jumps.  So, for example, a Relative
       Jump, followed by data, followed by a Write instruction that references
       the data as the source address will be rendered as though the data were
       instructions.  The only case where this does not hold is when the jump is
       the first instruction in the program.

    4. The algorithm also does not recognize the leading part of a program if
       the starting address points into the middle of the program, even if
       subsequent jump targets precede the start.  Handling either of these
       cases would require two passes through the program -- once to ascertain
       all of the execution paths and mark the instructions, and another tp
       render the program from the starting to ending addresses.
*/

static void trace_program (HP_WORD starting_address)
{
CP_OPCODE opcode;
t_stat    status;
t_value   jump, displacement, control;
t_addr    address, last, target;

address = last = (t_addr) starting_address;             /* initialize the instruction address */

do {
    target = address;                                   /* save the instruction address */

    mem_examine (&sim_eval [0], address + 0, NULL, 0);  /* get the (first) two words */
    mem_examine (&sim_eval [1], address + 1, NULL, 0);  /*   of the channel instruction */

    opcode = CPI_OPCODE (sim_eval [0]);                 /* save the instruction opcode */

    hp_trace (&cpp_dev, TRACE_OPND, BOV_FORMAT "  ",    /* print the address and the instruction opcode */
              0, address, sim_eval [0]);

    status = fprint_cpi (sim_deb, sim_eval, 0, address, NULL);  /* print the channel instruction */

    if (status == SCPE_ARG)                                 /* if the print failed */
        fprint_val (sim_deb, sim_eval [0], cpp_dev.dradix,  /*   then print the instruction */
                    cpp_dev.dwidth, PV_RZRO);               /*     in numeric format */

    if (sim_deb == stdout)                              /* if debug output is to the (raw) console */
        fputc ('\r', sim_deb);                          /*   then insert a carriage return */

    fputc ('\n', sim_deb);                              /* end the trace with a newline */

    address += -(int32) status + 1;                     /* point at the next instruction */

    switch (opcode) {                                   /* dispatch for postprocessing on the opcode */

        case Relative_Jump:
            if ((address + sim_eval [1] & LA_MASK) > last)      /* if the target is beyond the farthest address */
                if (last == (t_addr) starting_address)          /*   then if the program starts with the jump */
                    address = last = address + sim_eval [1]     /*     then restart tracing at the target */
                                       & LA_MASK;
                else                                            /*   otherwise */
                    last = address + sim_eval [1] & LA_MASK;    /*     reset the upper limit to the target */

            else if (address > last)                            /* otherwise if the jump is the last instruction */
                return;                                         /*   then it jumps backward and the trace is complete */
            break;


        case Device_Specified_Jump:
            target = address + (int32) status + 1;      /* point back at the first jump target */

            do {                                                    /* for each jump target */
                mem_examine (&jump, target++ & LA_MASK, NULL, 0);   /*   get the displacement */
                jump = (t_value) address + jump & LA_MASK;          /*      and the absolute address */

                if ((t_addr) jump > last)               /* if the target is beyond the farthest address */
                    last = (t_addr) jump;               /*   then reset the upper limit to the target */
                }
            while (target < address);                   /* loop until all displacements are processed */
            break;


        case Write:
        case Write_Control:
            target = address + 2 & LA_MASK;             /* a return to P + 2 is possible */

            if (target > last)                          /* if the potential target is beyond the current limit */
                last = target;                          /*   then reset the upper limit */
            break;


        case Read:
        case Read_Control:
            mem_examine (&displacement, target + 2, NULL, 0);   /* get the termination displacement */
            displacement = UPPER_BYTE (displacement);           /*   and reposition to the lower byte */

            if ((displacement & D8_SIGN) == 0)              /* if the displacement is positive */
                target = address + displacement & LA_MASK;  /*   then determine the termination target */
            else                                            /* otherwise */
                target = address + 2 & LA_MASK;             /*   a return to P + 2 is possible */

            if (target > last)                              /* if the potential target is beyond the current limit */
                last = target;                              /*   then reset the upper limit */
            break;


        case Execute_DMA:
            mem_examine (&control, target + 3, NULL, 0);    /*   then get the control word */

            if (control & CPI_DIRECTION)                    /* if this is an outbound transfer */
                target = address + 2 & LA_MASK;             /*   then a return to P + 2 is possible */

            else {                                                  /* otherwise it's an inbound transfer */
                mem_examine (&displacement, target + 2, NULL, 0);   /*   so get the termination displacement */
                displacement = UPPER_BYTE (displacement);           /*     and reposition to the lower byte */

                if ((displacement & D8_SIGN) == 0)              /* if the displacement is positive */
                    target = address + displacement & LA_MASK;  /*   then determine the termination target */
                else                                            /* otherwise */
                    target = address + 2 & LA_MASK;             /*   a return to P + 2 is possible */
                }

            if (target > last)                              /* if the potential target is beyond the current limit */
                last = target;                              /*   then reset the upper limit */
            break;


        case Interrupt:
            if (address > last)                         /* if we are beyond the farthest address */
                if (sim_eval [0] & CPI_HALT)            /*   then if the program halts */
                    return;                             /*     then the trace is complete */
                else                                    /*   otherwise the program continues */
                    last = address;                     /*     so reset the upper limit */
            break;


        case Invalid:
            return;                                     /* an invalid instruction ends the trace */


        default:                                        /* for all other instructions */
            if (address > last)                         /*   if we are beyond the farthest address */
                last = address;                         /*     then reset the upper limit */
            break;
        }
    }
while (TRUE);                                           /* continue until the end of the program is reached */

return;
}


/* Trace one channel instruction.

   This routine is called to print the currently executing channel instruction.
   On entry, the "channel" parameter contains the channel number, "instruction"
   contains the first word of the instruction, "operand" contains the second
   word of the instruction, and "drt0" contains the channel program pointer.
   The routine assumes that the debug output device (sim_deb) is open and the
   CMD trace option is in effect when it is called.
*/

static void trace_instruction (uint32 channel, HP_WORD instruction, HP_WORD operand, HP_WORD drt0)
{
sim_eval [0] = instruction;                             /* save the instruction that will be executed */
sim_eval [1] = operand;                                 /*   and the following word for evaluation */

hp_trace (&cpp_dev, TRACE_CMD, "Executing channel %u ", channel);

if (fprint_cpi (sim_deb, sim_eval, 0, drt0, NULL) > SCPE_OK)    /* print the mnemonic; if that fails */
    fprint_val (sim_deb, sim_eval [0], cpp_dev.dradix,          /*   then print the value */
                cpp_dev.dwidth, PV_RZRO);                       /*     in numeric format */

if (sim_deb == stdout)                                  /* if debug output is to the (raw) console */
    fputc ('\r', sim_deb);                              /*   then insert a carriage return */

fputc ('\n', sim_deb);                                  /* end the trace with a newline */

return;
}
