/* hp3000_imb.c: HP 3000 Intermodule Bus 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
   18-May-22    JDB     Created

   References:
     - HP 3000 Series 40/44 Computer Systems Reference/Training Manual
         (30090-90001, July 1981) [pp. 3-2 to 3-7]


   The Intermodule Bus (IMB) is the general backplane bus for the HP-IB HP 3000
   computer systems.  It is also used in the HP 30341A HP-IB Interface Module
   ("Starfish").  For the computer systems, it connects the CPU, main memory,
   and I/O channels.  Within the Starfish, the processor, General I/O Channels
   (GICs), and the Intermodule Bus Adapter (IMBA) communicate via the IMB, with
   the IMBA serving as the gateway to the Series III main memory.

   The IMB consists of 66 signal lines and 12 priority lines.  The signal lines
   comprise a 3-bit opcode bus, a 16-bit address bus, an 8-bit address extension
   bus, a 16-bit data bus, and assorted handshake, interrupt, and system status
   signals.

   IMB commands are defined by an operation code asserted on the opcode bus.
   For memory reads and writes, the address and address extension buses form a
   24-bit address, and the data bus supplies or accepts the 16-bit data,
   respectively. For I/O operations, the address bus gives the command code,
   register number, and channel number, and the data bus supplies or accepts the
   16-bit data, respectively.  Channel number 0 is reserved.

   The command format is:

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

   The combination of IMB opcode and command defines each of the I/O operations,
   as follows:

     Mnemonic  Opcode  Command  Operation
     --------  ------  -------  --------------------------------
        --      000      --     Memory Read
        --      001      --     Memory Read/Write Ones
        --      010      --     Memory Write
        --      011      --     (reserved)
        --      100      --     I/O Read
       RIOC     100     0000      Read I/O Data
       OBII     100     0010      Obtain Interrupt Information
       OBSI     100     0100      Obtain Service Information
        --      100     0110      (reserved)
       IPOLL    100     1000      Interrupt Poll
       ROCL     100     1010      Roll Call
       SPOL1    100     1100      Service Poll 1
       SPOL2    100     1110      Service Poll 2
        --      101      --     Memory Write Control/Read Status
        --      110      --     I/O Write
       WIOC     110     0000      Write I/O Data
       INIT     110     0010      Initialize Channel
       SIOP     110     0100      Start I/O Program
       HIOP     110     0110      Halt I/O Program
       SMSK     110     1000      Set Interrupt Mask
       IOCL     110     1010      I/O Clear
        --      110     1100      (reserved)
        --      110     1110      (reserved)
        --      111      --     (reserved)

   Note that although the command code is four bits wide, the LSB is always 0
   and is usually not decoded in hardware.

   Global commands have address bit 0 = 1 and omit the channel number.  All
   channels respond to these commands.  Local commands have address bit 0 = 0,
   and only the channel addressed by address bits 9-12 will respond to the
   command.

   The value present on the data bus depends on the I/O command.  Data is
   channel-dependent for the Read I/O Data and Write I/O Data commands, and is
   not used for the I/O Clear command.

   The command and data formats are as follows:

   RIOC -- Read I/O Channel

        0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      | 0   0   0   0 |   Register    | - |    Channel    | -   -   - | address
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      |                        Register Value                         | data
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   The value of the addressed register is returned on the data bus.


   OBII -- Obtain Interrupt Information

        0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      | 0   0   1   0 | -   -   -   - | - |    Channel    | -   -   - | address
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      | 0   0   0   0   0   0   0   0 | N |    Channel    |  Device   | data
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     N = Channel and device numbers are not valid

   Obtain Interrupt Information is issued to the highest-priority channel that
   is asserting IRQ, as determined by the Interrupt Poll command.  The channel
   returns the number of the highest-priority device that has an interrupt
   pending.  The returned word is used to determine the DRT entry for the
   external interrupt.  If the Not Valid bit is set, the Channel and Device
   numbers are invalid, as no interrupt request is pending.


   OBSI -- Obtain Service Information

        0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      | 0   1   0   0 | -   -   -   - | - |    Channel    | -   -   - | address
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      | 0   0   0 | T | A | S | D | C | N |    Channel    |  Device   | data
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     T = Timeout
     A = DMA Abort
     S = SRQ Request
     D = Device Request
     C = Channel Request
     N = Channel and device numbers are not valid

   The response indicates the channel and device requesting service from the
   controller and the reason for the request.  If the Not Valid bit is set, the
   Channel and Device numbers are invalid, as no service request is pending.


   IPOLL -- Interrupt Poll

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

   Interrupt Poll is issued in response to IRQ assertion.  Each channel that is
   asserting IRQ asserts the data bit corresponding to its channel number.


   ROCL -- Roll Call

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

   Each channel that is present on the IMB asserts the data line corresponding
   to its channel number.


   SPOL1 -- Service Poll 1
   SPOL2 -- Service Poll 2

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

   Each channel that is asserting CSRQ1 or CSRQ2 asserts the data bit
   corresponding to its channel number.


   WIOC -- Write I/O Channel

        0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      | 0   0   0   0 |   Register    | - |    Channel    | -   -   - | address
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      |                        Register Value                         | data
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   The value present on the data bus is written to the addressed register.


   INIT -- Initialize Channel

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

   The specified channel is reset to its idle state.  The value on the data bus
   is ignored.


   SIOP -- Start I/O Program
   HIOP -- Halt I/O Program

        0 | 1   2   3 | 4   5   6 | 7   8   9 |10  11  12 |13  14  15
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      | 0   1   x   0 | -   -   -   - | - |    Channel    | -   -   - | address
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
      | -   -   -   -   -   -   -   -   -   -   -   - | S |  Device   | data
      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     S = New status

   An SIOP or HIOP sets (S = 0) or clears (S = 1) the bit in the channel's New
   Status register corresponding to the supplied device number.  Setting the bit
   causes the channel to assert CSRQ and set bit 6 (device request) in the OBSI
   response.  This is used to inform the Channel Program Processor that the CPU
   has changed the program status, e.g., now starting or now halting.


   SMSK -- Set Interrupt Mask

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

   The data bit corresponding to the channel's assigned address disables (0) or
   enables (1) channel interrupts by inhibiting or allowing IRQ assertion.


   IOCL -- I/O Clear

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

   I/O Clear causes all channels to reset to their idle states.  The value on
   the data bus is ignored.


   This module exports the following global functions:

     imb_cycle    -- execute a general IMB memory or I/O cycle
     imb_io_cycle -- execute an IMB I/O cycle
     imb_io_read  -- execute an IMB I/O read cycle
     imb_io_write -- execute an IMB I/O write cycle

   The imb_cycle routine executes one IMB cycle and returns the values on the
   data bus and signal lines.  The imb_io_cycle routine executes an I/O read or
   write cycle, depending on the I/O command supplied.  It returns only the data
   bus value.  The imb_io_read and imb_io_write routines execute I/O read or
   write cycles, respectively, and return only the data bus value.

     imb_assert_CSRQ -- assert the CSRQ1 signal asynchronously
     imb_assert_IRQ  -- assert the IRQ signal asynchronously

   These functions assert the CSRQ1 and IRQ signals asynchronously.  Synchronous
   assertion of the signals in response to an IMB cycle is also supported.

   This module exports the following global state variables:

     imb_channel_request_set   -- the set of channels requesting service
     imb_interrupt_request_set -- the set of channels requesting interrupts

   These are provided to the Channel Program Processor to direct its actions.
*/



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



/* Program constants */

#define MEMORY              0                   /* the memory controller is pseudo-channel 0 */
#define IMBA                1                   /* the IMBA is permanently set to channel 1 */

#define CPP_DELAY           uS (5)              /* processing delay from CSRQ assertion to CPP entry */


/* IMB local data structures */

static const BITSET_NAME imb_inbound_names [] = {   /* Inbound signal names, in INBOUND_SIGNAL order */
    "BRQ",                                          /*   000000000001 */
    "ADO",                                          /*   000000000002 */
    "DDO",                                          /*   000000000004 */
    "WAIT",                                         /*   000000000010 */
    "SRST",                                         /*   000000000020 */
    "PON",                                          /*   000000000040 */
    "PFW",                                          /*   000000000100 */
    "PRI"                                           /*   000000000200 */
    };

static const BITSET_FORMAT imb_inbound_format =     /* names, offset, direction, alternates, bar */
    { FMT_INIT (imb_inbound_names, 0, lsb_first, no_alt, no_bar) };


static const BITSET_NAME imb_outbound_names [] = {  /* Outbound signal names, in OUTBOUND_SIGNAL order */
    "BACK",                                         /*   000000000001 */
    "ADN",                                          /*   000000000002 */
    "DDN",                                          /*   000000000004 */
    "IRQ",                                          /*   000000000010 */
    "CSRQ1",                                        /*   000000000020 */
    "CSRQ2",                                        /*   000000000040 */
    "DNV",                                          /*   000000000100 */
    "PER",                                          /*   000000000200 */
    "PRO",                                          /*   000000000400 */
    "PCRY"                                          /*   000000001000 */
    };

static const BITSET_FORMAT imb_outbound_format =    /* names, offset, direction, alternates, bar */
    { FMT_INIT (imb_outbound_names, 0, lsb_first, no_alt, no_bar) };


static const char *imb_opcode_names [] = {      /* opcode names, in IMB_OPCODE order */
    "Memory Read",                              /*   000 = Memory Read */
    "Memory Read/Write Ones",                   /*   001 = Memory Read/Write Ones */
    "Memory Write",                             /*   010 = Memory Write */
    "(reserved)",                               /*   011 = (reserved) */
    "I/O Read",                                 /*   100 = I/O Read */
    "Memory Status",                            /*   101 = Memory Write Control/Read Status */
    "I/O Write",                                /*   110 = I/O Write */
    "(reserved)"                                /*   111 = (reserved) */
    };


static const char *imb_io_command_names [] = {  /* I/O command names, in IMB_IO_COMMAND order */
    "RIOC",                                     /*   0000 = Read I/O Data */
    "WIOC",                                     /*   0001 = Write I/O Data */
    "OBII",                                     /*   0010 = Obtain Interrupt Information */
    "INIT",                                     /*   0011 = Initialize Channel */
    "OBSI",                                     /*   0100 = Obtain Service Information */
    "SIOP",                                     /*   0101 = Start I/O Program */
    "(reserved)",                               /*   0110 = (reserved) */
    "HIOP",                                     /*   0111 = Halt I/O Program */
    "IPOLL",                                    /*   1000 = Interrupt Poll */
    "SMSK",                                     /*   1001 = Set Interrupt Mask */
    "ROCL",                                     /*   1010 = Roll Call */
    "IOCL",                                     /*   1011 = I/O Clear */
    "SPOL1",                                    /*   1100 = Service Poll 1 */
    "(reserved)",                               /*   1101 = (reserved) */
    "SPOL2",                                    /*   1110 = Service Poll 2 */
    "(reserved)"                                /*   1111 = (reserved) */
    };


/* IMB dispatch table.

   In hardware, IMB I/O commands are either global or local.  A global command
   is decoded by all channels, each of which responds as appropriate.  A local
   command is decoded and responded only by the addressed channel; the other
   channels ignore it.  Memory read and write commands are global in the sense
   that any memory controller may respond, but only the one hosting the memory
   at the addressed location will respond.

   A direct simulation of the hardware would send all IMB commands to all of the
   IMB interface routines present on the system.  This is inefficient, as most
   commands are local, and only one device will respond to memory commands.  So
   instead, these commands are sent only to the IMB interface routine of the
   specified channel.

   To avoid having to scan the device list to find the addressed channel each
   time an IMB command is executed, an IMB dispatch table is filled in as part
   of run-time initialization during the instruction execution prelude.  The
   table is indexed by channel number (00-17 octal) and contains pointers to the
   device and DIB structures associated with each channel.  The dispatch table
   also contains a reference counter that is used during initialization to
   ensure that each channel number is unique.

   Initialization is performed during each "sim_instr" call, as the channel
   assignments may have been changed by the user at the SCP prompt.


   Implementation notes:

    1. The table contains constant pointers, but "const" cannot be used here, as
       "hp_trace" calls "sim_dname", which takes a variable device pointer even
       though it does not change anything.
*/

typedef struct {                                /* IMB access table entry */
    DEVICE  *devptr;                            /*   a pointer to the DEVICE structure */
    DIB     *dibptr;                            /*   a pointer to the DIB structure */
    uint32  references;                         /*   a count of the references to this channel */
    } IMB_TABLE;

static IMB_TABLE dispatcher [CHAN_MAX + 1];     /* IMB cycle dispatcher, indexed by channel number */


/* IMB global state */

uint32 imb_channel_request_set   = 0;           /* the set of channels requesting service */
uint32 imb_interrupt_request_set = 0;           /* the set of channels requesting interrupts */


/* IMB local utility routines */

static IMB_OUTBOUND io_cycle  (uint32 channel, IMB_INBOUND inbound);
static IMB_OUTBOUND mem_cycle (IMB_INBOUND inbound);



/* IMB global routines */


/* Set up the IMB dispatch table.

   This routine is called by the CPU instruction prelude to set up the IMB
   dispatch table prior to beginning execution.  The table is indexed by channel
   number, and each entry records pointers to the device and DIB structures
   associated with that channel.  This allows fast access to the device trace
   flags and to the device interface routines, respectively, by the IMB cycle
   executors.

   After initializing the dispatch table, a check is made for channel conflicts.
   These occur if two or more devices are assigned to the same channel number.

   Each channel number must be unique among the enabled devices.  This
   requirement is checked as part of the instruction execution prelude; this
   allows the user to exchange two channel numbers simply by setting each device
   to the other's channel.  If conflicts were enforced instead at the time the
   channels were entered, the first device would have to be set to an unused
   channel number before the second could be set to the first device's channel.

   If any conflicts exist, the device table is scanned to find the DIBs whose
   channel numbers match the conflicting values, and the device names associated
   with the conflicts are printed.

   The routine returns FALSE to prevent execution if conflicts exist and TRUE if
   execution may proceed.


   Implementation notes:

    1. This routine must be called explicitly from the CPU instruction prelude,
       because the IMB is not a device and therefore does not have a DIB that
       can reference a run-time initializer routine.

    2. When this routine is called from the instruction prelude, the console and
       optional log file have already been put into "raw" output mode.  As a
       consequence, newlines are not translated to the correct line ends on
       systems that require it.  Before reporting a conflict, "sim_ttcmd" is
       called to restore the console and log file translation. This is OK
       because a conflict will abort the run and return to the command line
       anyway.

    3. sim_dname is called instead of using dptr->name directly to ensure that
       we pick up an assigned logical device name.

    4. For the Starfish, the IMBA responds to the IMB memory commands.  Because
       we need to dispatch them to the IMBA, we reserve element 0 for this
       purpose (channel 0 does not exist), and we set it to the values in
       element 1, as the IMBA is permanently assigned to channel 1.  When we
       implement the HP-IB 3000s, we will need a better way to identify the
       memory controller on the bus -- perhaps with a device or DIB flag.
*/

t_bool imb_initialize (void)
{
DEVICE *dptr;
DIB    *dibptr;
uint32 dev, count, channel;
t_bool is_conflict = FALSE;

memset (&dispatcher, 0, sizeof dispatcher);             /* clear the IMB dispatcher table */

for (dev = 0; sim_devices [dev] != NULL; dev++) {       /* loop through all of the devices */
    dptr = sim_devices [dev];                           /* get a pointer to the device */
    dibptr = (DIB *) dptr->ctxt;                        /*   and to that device's DIB */

    if (dibptr != NULL                                  /* if the DIB exists */
      && dibptr->imb_interface != NULL                  /*   and the channel interface is defined */
      && not (dptr->flags & DEV_DIS)) {                 /*   and the device is enabled */
        channel = dibptr->channel_number;               /*     then get the device's channel number */

        if (channel > 0) {                              /* if the device resides on the IMB */
            dispatcher [channel].devptr = dptr;         /*   then set the device pointer */
            dispatcher [channel].dibptr = dibptr;       /*     and the DIB pointer into the table */

            dispatcher [channel].references++;          /* increment the count of references */

            if (dispatcher [channel].references > 1)    /* if more than one reference to this channel exists */
                is_conflict = TRUE;                     /*   then a conflict is present */
            }
        }
    }

dispatcher [MEMORY] = dispatcher [IMBA];                /* add the entry for the memory controller */

if (is_conflict) {                                      /* if a conflict exists */
    sim_ttcmd ();                                       /*   then restore the console and log I/O modes */

    for (channel = 0; channel <= CHAN_MAX; channel++)   /* search the dispatch table for the next conflict */
        if (dispatcher [channel].references > 1) {      /* if a conflict is present for this channel */
            count = dispatcher [channel].references;    /*   then get the number of conflicting devices */

            cprintf ("Channel %u conflict (", channel); /* report the multiply-assigned channel */

            for (dev = 0; sim_devices [dev] != NULL; dev++) {   /* loop through all of the devices */
                dptr = sim_devices [dev];                       /* get a pointer to the device */
                dibptr = (DIB *) dptr->ctxt;                    /*   and to that device's DIB */

                if (dibptr != NULL                                  /* if the DIB exists */
                  && dibptr->imb_interface != NULL                  /*   and the channel interface is defined */
                  && not (dptr->flags & DEV_DIS)                    /*   and the device is enabled */
                  && dibptr->channel_number == channel) {           /*   and this entry conflicts */
                    if (count < dispatcher [channel].references)    /*     then report it to the console */
                        cputs (" and ");

                    cputs (sim_dname (dptr));           /* report the conflicting device name */
                    count = count - 1;                  /*   and drop the count of remaining conflicts */

                    if (count == 0)                     /* if all devices have been reported */
                        break;                          /*   then there's no need to look further */
                    }
                }                                       /* loop until all conflicting devices are reported */

            cputs (")\n");                              /* tie off the line */
            }                                           /*   and continue to look for other conflicting channels */

    return FALSE;                                       /* report that initialization has failed */
    }

else                                                    /* otherwise no conflicts were found */
    return TRUE;                                        /*   so return success */
}


/* Request channel service.

   This routine is called by a device interface to request service from the
   channel.  It is called either directly by the interface or indirectly by the
   imb_cycle routine in response to a CSRQ1 signal returned by the interface.  A
   direct call is needed for asynchronous assertion, e.g., in response to an
   event service call.  Synchronous assertion, i.e., in response to an interface
   call, is made by returning the CSRQ1 signal to conclude an IMB cycle.  The
   routine corresponds in hardware to asserting CSRQ1 on the Intermodule Bus.

   On entry, the bit corresponding to the supplied channel number is set in the
   channel request set.  This enables the Channel Program Processor to service
   the interface after the specified entry delay has expired, assuming that the
   interface has priority.


   Implementation notes:

    1. Entry into the CPP from the CPU instruction loop is delayed by setting
       the global down-counter to the desired delay in event ticks.  Scheduling
       an event would also work, but then the CPP unit would have to be global.
       So either way, some global import would be needed.

    2. CPP entry must be delayed after CSRQ assertion, rather than simply
       entering on the next pass through the CPU instruction loop.  The reason
       is that the CPP Identify command sets up a PHI interrupt on data
       reception, and this asserts CSRQ when the first byte arrives from the
       device.  The CPP code then reads both bytes because it assumes that the
       device is fast enough.  But we get only the first ID byte; the second
       FIFO read attempt gets DNV because the device's data transfer event is
       still scheduled.  Write Loopback also fails, as devices schedule
       completion after the last loopback byte is received.  If the CSRQ is
       recognized immediately, the CPP will send an Unlisten to end the command
       before the device event has been serviced.  This causes a transaction
       error in the device.
*/

void imb_assert_CSRQ (uint32 channel)
{
const uint32 channel_bit = BIT (channel);               /* get the bit associated with the channel */

if ((imb_channel_request_set & channel_bit) == 0) {     /* if CSRQ is not pending for the channel */
    imb_channel_request_set |= channel_bit;             /*   then set the channel request bit */

    tpprintf (dispatcher [channel].devptr, TRACE_IMBUS, "Channel %d asserts CSRQ1\n", channel);

    cpp_request = CPP_DELAY;                            /* delay CPP entry by the required amount */
    }

return;
}


/* Request an interrupt.

   This routine is called by device interfaces to request an external interrupt.
   It is called either directly by the interface or indirectly by the imb_cycle
   routine in response to an IRQ signal returned by the interface.  A direct
   call is needed for asynchronous assertion, e.g., in response to an event
   service call.  Synchronous assertion, i.e., in response to an interface call,
   is made by returning the IRQ signal to conclude an IMB cycle.  The routine
   corresponds in hardware to asserting IRQ on the Intermodule Bus.

   On entry, the bit corresponding to the supplied channel number is set in the
   interrupt request set.  This enables the Channel Program Processor to service
   the interface after the specified entry delay has expired, assuming that the
   interface has priority.

   If the IRQ and CSRQ lines are asserted simultaneously, IRQ has priority.


   Implementation notes:

    1. See the notes for imb_assert_CSRQ above for details regarding the CPP
       entry delay.
*/

void imb_assert_IRQ (uint32 channel)
{
const uint32 channel_bit = BIT (channel);               /* get the bit associated with the channel */

if ((imb_interrupt_request_set & channel_bit) == 0) {   /* if IRQ is not pending for the channel */
    imb_interrupt_request_set |= channel_bit;           /*   then set the interrupt request bit */

    tpprintf (dispatcher [channel].devptr, TRACE_IMBUS, "Channel %d asserts IRQ\n", channel);

    cpp_request = CPP_DELAY;                            /* delay CPP entry by the required amount */
    }

return;
}


/* Execute a general IMB cycle.

   This routine executes one memory or I/O cycle on the Intermodule Bus.  On
   entry, the opcode parameter specifies the type of cycle to execute, and the
   address and data parameters supply direct values for a memory cycle or supply
   additional encoded parameters for an I/O cycle.  For the latter, the address
   parameter is formatted as follows:

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

   I/O cycle commands are either global (all channels respond) or local (only
   the addressed channel responds).  However, rather than send all commands to
   all present channels and letting each decide whether to respond, an
   optimization is employed.

   Global I/O write cycles (SMSK and IOCL) are sent to all present channels in
   sequence.  The ROCL global I/O read cycle is handled the same way.  The other
   global I/O read cycles (IPOLL, SPOL1, and SPOL2) can be handled as local
   commands because they are sent in response to asynchronous assertion of the
   IRQ and CSRQ signals, whose implementations receive the asserting channel
   number.  Request bit sets are maintained for these, so that the I/O cycle
   need only be sent to the highest priority channel, which will be passed to us
   in the channel field of the address parameter.  Only if the designated
   channel responds negatively would the next-highest priority channel need to
   be consulted.

   Once determination of the channel target(s) is made, the IMB interface
   routine for each target device is called to initiate the cycle.  The supplied
   parameters are passed, along with a set of inbound signals.  The interface
   returns the state of the data bus plus a set of asserted outbound signals.
   The CSRQ1 and IRQ signals are used to set the channel bit in the request sets
   before returning to the caller.  A local IMB cycle directed to a channel that
   does not exist asserts the Data Not Valid signal in the return set.


   Implementation notes:

    1. Global I/O read cycles return data words whose bits correspond to channel
       numbers using the HP 3000 convention.  For example, channel 0 is
       represented by the octal value 100000, while channel 15 is 000001.  This
       is opposite to the convention used by the request bit sets themselves.

    2. A channel can return IRQ in response to an SMSK command if it sets the
       channel's mask bit with an interrupt pending.  Therefore, the global
       dispatcher must check for IRQ assertion.

    3. The IMB handshake signals (Address and Data Do, Address and Data Done)
       are exchanged with the channel interface, but the actual handshaking is
       implied rather than implemented explicitly in simulation.
*/

IMB_OUTBOUND imb_cycle (IMB_OPCODE opcode, IMB_ADDRESS address, IMB_DATA data)
{
uint32       channel;
IMB_OUTBOUND partial;
IMB_OUTBOUND outbound = { 0, NO_SIGNALS };
IMB_INBOUND  inbound  = { opcode, address, data, ADO | DDO | PRI };

if (opcode == IMB_IO_Read || opcode == IMB_IO_Write) {  /* if this is an I/O cycle */
    channel = IMB_CHANNEL (address);                    /*   then decode the channel number */

    if (address & IMB_GLOBAL && channel == 0)               /* if the command must be sent to all channels */
        for (channel = 1; channel <= CHAN_MAX; channel++) { /*   then loop through the dispatch list */
            if (dispatcher [channel].dibptr != NULL) {      /*     and if the channel is present */
                partial = io_cycle (channel, inbound);      /*       then handshake the cycle */

                if (partial.signals & IRQ)                          /* if the interface asserted IRQ */
                    imb_assert_IRQ (channel);                       /*   then set the request */
                else                                                /* otherwise */
                    imb_interrupt_request_set &= ~BIT (channel);    /*   clear the request */

                if (not (partial.signals & DNV))        /* if the returned data (bit) is valid */
                    outbound.data |= partial.data;      /*   then merge it into the data bus */

                outbound.signals |= partial.signals;    /* accumulate the outbound signal set */
                }
            }                                           /* loop until all channels are notified */

    else if (dispatcher [channel].dibptr == NULL)       /* otherwise if the channel does not exist */
        outbound.signals = DNV;                         /*   then assert the DNV signal */

    else {                                              /* otherwise it's a local command */
        outbound = io_cycle (channel, inbound);         /*   so handshake with the specified channel */

        if (outbound.signals & IRQ)                         /* if the interface asserted IRQ */
            imb_assert_IRQ (channel);                       /*   then set the request */
        else                                                /* otherwise */
            imb_interrupt_request_set &= ~BIT (channel);    /*   clear the request */

        if (outbound.signals & CSRQ1)                   /* if the interface asserted CSRQ1 */
            imb_assert_CSRQ (channel);                  /*   then set the request */
        else                                            /* otherwise */
            imb_channel_request_set &= ~BIT (channel);  /*   clear the request */
        }
    }

else                                                    /* otherwise it's a memory opcode */
    outbound = mem_cycle (inbound);                     /*   so issue a memory cycle */

return outbound;                                        /* return the outbound data and signal set */
}


/* Execute an IMB I/O cycle.

   This routine executes one I/O read or write cycle on the Intermodule Bus.  On
   entry, the command parameter specifies the I/O command,which implies the type
   of I/O cycle to execute, the channel parameter specifies the receiving
   channel number, and the data parameter provides the data bus value for I/O
   write commands.  The routine returns the data bus value for I/O read
   commands.  The outbound signal set is discarded, so this routine cannot be
   used where that set is needed.

   The routine places the command and channel values onto the IMB address bus,
   as follows:

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

   The register field is not set, so this routine is not suitable for reading or
   writing channel registers.
*/

IMB_DATA imb_io_cycle (IMB_IO_COMMAND command, uint32 channel, IMB_DATA data)
{
uint32     address;
IMB_OPCODE opcode;

if (IS_WRITE_COMMAND (command))                         /* if the command is a write command */
    opcode = IMB_IO_Write;                              /*   then execute an I/O write cycle */
else                                                    /* otherwise */
    opcode = IMB_IO_Read;                               /*   execute an I/O read cycle */

address = TO_IMB_COMMAND (command) | TO_IMB_CHANNEL (channel);  /* encode the address bus value */

return imb_cycle (opcode, address, data).data;          /* execute the cycle and return the outbound data */
}


/* Execute a Write I/O Channel command.

   This routine executes an IMB I/O Write cycle with a Write I/O Channel (WIOC)
   command encoded on the address bus.  The routine places the supplied
   channel and register parameter values, along with the WIOC command, onto the
   IMB address bus, as follows:

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

   The outbound signals from the IMB cycle are discarded.
*/

void imb_io_write (uint32 channel, uint32 Register, IMB_DATA data)
{
uint32 address;

address = TO_IMB_COMMAND (WIOC)                         /* encode the command */
            | TO_IMB_REGISTER (Register)                /*   and the register number */
            | TO_IMB_CHANNEL (channel);                 /*     and the channel number onto the bus */

imb_cycle (IMB_IO_Write, address, data);                /* execute the write cycle */

return;
}


/* Execute a Read I/O Channel command.

   This routine executes an IMB I/O Read cycle with a Read I/O Channel (RIOC)
   command encoded on the address bus.  The routine places the supplied channel
   and register parameter values, along with the RIOC command, onto the IMB
   address bus, as follows:

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

   The outbound data bus value from the IMB cycle is returned.  The outbound
   signals are discarded.
*/

IMB_DATA imb_io_read (uint32 channel, uint32 Register)
{
uint32 address;

address = TO_IMB_COMMAND (RIOC)                         /* encode the command */
            | TO_IMB_REGISTER (Register)                /*   and the register number */
            | TO_IMB_CHANNEL (channel);                 /*     and the channel number onto the bus */

return imb_cycle (IMB_IO_Read, address, 0).data;        /* execute the read cycle and return the data */
}



/* IMB local utility routines */


/* Execute an IMB I/O cycle.

   This helper routine dispatches the inbound opcode, address, data, and signal
   set to the channel indicated.  The opcode must be an I/O read or write.  If
   IMB tracing is enabled for the associated device, the inbound and outbound
   cycle state is traced.  the outbound data bus value and signal set are
   returned.
*/

static IMB_OUTBOUND io_cycle (uint32 channel, IMB_INBOUND inbound)
{
const IMB_TABLE *dispatch = &dispatcher [channel];      /* the pointer to the dispatch entry */
uint32          Register;
IMB_IO_COMMAND  command;
IMB_OUTBOUND    outbound;

if (TRACINGP (dispatch->devptr, TRACE_IMBUS)) {
    command  = IMB_COMMAND  (inbound.address, inbound.opcode);
    Register = IMB_REGISTER (inbound.address);

    hp_trace (dispatch->devptr, TRACE_IMBUS,
              "Channel %d received opcode %s command %s "
              "register %X data %06o with signals %s\n",
              channel, imb_opcode_names [inbound.opcode],
              imb_io_command_names [command], Register, inbound.data,
              fmt_bitset (inbound.signals, imb_inbound_format));
    }

outbound = dispatch->dibptr->imb_interface (dispatch->dibptr, inbound); /* dispatch the cycle */

tpprintf (dispatch->devptr, TRACE_IMBUS,
          "Channel %d returned data %06o with signals %s\n",
          channel, outbound.data,
          fmt_bitset (outbound.signals, imb_outbound_format));

return outbound;                                        /* return the outbound data and signal set */
}


/* Execute an IMB memory cycle.

   This helper routine dispatches the inbound opcode, address, data, and signal
   set to the channel indicated.  The opcode must be a memory read or write.  If
   IMB tracing is enabled for the associated device, the inbound and outbound
   cycle state is traced.  the outbound data bus value and signal set are
   returned.
*/

static IMB_OUTBOUND mem_cycle (IMB_INBOUND inbound)
{
const IMB_TABLE *dispatch = &dispatcher [MEMORY];       /* the pointer to the dispatch entry */
IMB_OUTBOUND    outbound;

tpprintf (dispatch->devptr, TRACE_IMBUS,
          "Memory controller received opcode %s address %08o data %06o "
          "with signals %s\n",
          imb_opcode_names [inbound.opcode],
          inbound.address, inbound.data,
          fmt_bitset (inbound.signals, imb_inbound_format));

outbound = dispatch->dibptr->imb_interface (dispatch->dibptr, inbound); /* dispatch the cycle */

tpprintf (dispatch->devptr, TRACE_IMBUS,
          "Memory controller returned data %06o with signals %s\n",
          outbound.data, fmt_bitset (outbound.signals, imb_outbound_format));

return outbound;                                        /* return the outbound data and signal set */
}
