/* hp3000_imba.c: HP 3000 30340A Intermodule Bus Adapter 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.

   IMBA         30340A Intermodule Bus Adapter

   09-Jun-23    JDB     First release
   18-May-22    JDB     Created

   References:
     - HP 3000 Series III Engineering Diagrams Set
         (30000-90141, April 1980) [pp. 3-28 to 3-30]
     - HP 3000 Series 40/44 Computer Systems Reference/Training Manual
         (30090-90001, July 1981) [pp. 3-2 to 3-7]
     - HP 3000 Series 39/40/42/44/48 Computer Systems Engineering Diagrams Set
         (30090-90034, October 1984) [pp. 3-25 to 3-29]


   The HP 30340A Intermodule Bus Adapter is a component of the 30341A HP-IB
   Interface Module ("Starfish").  The Starfish chassis contains these PCAs:

     - 30340-60002 Intermodule Bus Adapter (IMBA)
     - 30070-60012 Processor
     - 31000-60053 Bus Interface Controller (BIC)
     - 31262-60001 General I/O Channel (GIC)
     - 30340-60001 Backplane (6 position)

   The IMBA connects via flat ribbon cables to the Series III IOP bus, the
   IOP/Power bus, and to an additional 30030-60020 Port Controller installed in
   the Series III chassis.  The Processor is a Series 33 processor card
   containing modified firmware that implements the Channel Program Processor
   and Series 33 I/O instruction set.  The BIC and GIC are standard Series 33
   cards.

   Graphically, the module appears as follows:

                                                  Intermodule Bus
              +---------------+     +------------+-----------+----------------+
              |               |     |            |           |                |
              |               |     |            |           |                |
     +-----------------+  +-------------+  +-----------+  +-----+          +-----+
     |                 |  |             |  |           |  |     |          |     |
     | Port Controller |  | IMB Adapter |  | CPP / BIC |  | GIC |          | GIC |
     |                 |  |             |  |           |  |     |          |     |
     +-----------------+  +-------------+  +-----------+  +-----+          +-----+
              |                  |                           |                |
              |                  |                           |                |
       +-------------+     ------+------              +-------------+  +-------------+
       |             |        IOP Bus                 |             |  |             |
       | Main Memory |     IOP/Power Bus              | HP-IB Discs |  | HP-IB Tapes |
       |             |                                |             |  |             |
       +-------------+                                +-------------+  +-------------+

   The IMBA serves three functions.

   First, it enables the Series III to communicate with the Starfish processor
   that runs HP-IB channel programs.  Issuing an SIO order to the IMBA asserts
   the Channel Service Request (CSRQ) line on the IMB, causing the Starfish
   processor to run its Channel Program Processor (CPP) microcode.  The CPP
   retrieves the memory address of the channel program and other parameters from
   a "mailbox" area in main memory and initiates program execution.  Completion
   status is returned via the mailbox.

   Second, it permits the DMA controller on the Starfish's General I/O Channel
   (GIC) to read and write data in the Series III main memory.  The IMBA
   interprets IMB Memory Read and Memory Write cycles and passes them through to
   the dedicated Port Controller.  It reflects main memory parity errors back to
   the GIC via the IMB.

   Third, it allows the Starfish processor to assert the Series III Interrupt
   Request (INTREQ) line to cause an external interrupt.  Because the Starfish
   supports multiple devices and therefore multiple interrupt sources, the IMBA
   has a DEVNO response register that permits it to "masquerade" as different
   devices during an interrupt poll cycle.  The Starfish processor can program
   the IMBA to supply any desired device number, rather than its own device
   number that is fixed at 125 decimal (175 octal).

   The IMBA responds to four of the eight I/O commands presented on the IOP bus:

           IOCMD  Internal
     Inst  0 1 2  Signal      Action
     ----  - - -  ----------  ---------------
     CIO   0 0 1  DCONTSTB    Control I/O
     SIO   0 1 0  (STARTIO)   Start I/O
     IXIT  1 0 0  RESETINT    Reset Interrupt
     TIO   1 0 1  DSTATSTB    Test I/O

   Commands are enabled on the IMBA when the DEVNO lines present either 175
   octal (the device number of the IMBA) or 200 octal + the contents of the
   DEVNO response register.  The register value is set in response to an IMB
   WIOC command, as described below.

   The CIO and TIO words are structured as follows:

   IMBA Control Word Format (CIO):

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

   Where:

     M = master reset

   A master reset performs these actions:

    - clears the CSRQ flip-flop
    - clears the INTREQ flip-flop
    - clears the IMB handshake flip-flops
    - clears the IMB parity error flip-flop

   Master reset is also initiated by asserting PON or IORESET on the IOP bus,
   and by an IMB IOCL command.


   IMBA Status Word Format (TIO):

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

   Where:

     S = SIO OK (CSRQ flip-flop is clear)
     D = direct read/write I/O OK (always 1)
     V = +5 volts is present on the Intermodule Bus

   The RESETINT signal, generated by the IXIT command, clears the INTACK
   flip-flop.  An SIO command sets the CSRQ flip-flop, which asserts CSRQ on the
   IMB.

   The IMBA decodes five IMB commands:

     Opcode  Command  Mnemonic  Operation
     ------  -------  --------  --------------
      000      --        --     Memory Read
      010      --        --     Memory Write
      100     110x      SPOL1   Service Poll 1
      110     000x      WIOC    I/O Write Data
      110     101x      IOCL    I/O Clear

   SPOL1 gates the CSRQ flip-flop onto IMB DATA1 line.  Assertion indicates that
   channel 1 (the IMBA) is asserting CSRQ.  It also clears the CSRQ flip-flop at
   the end of the command.

   WIOC is additionally qualified by channel 1, i.e., the IMBA's IMB address.
   Assertion writes IMB DATA9-15 into the DEVNO response register and sets the
   interrupt request flip-flop.  The response register is gated onto the DEVNO
   lines when INTPOLLIN is asserted, so that the interrupt handler is selected
   from the DRT of the appropriate GIC device, rather than from the IMBA DRT.

   The MSB (DATA8) of the IMB WIOC data word is not stored in the register.
   During INTPOLLIN, DEVNO0 is set to 0.  During IOP command decoding, DEVNO0 is
   compared to 1, effectively adding 200 octal to the register value for the
   comparison.  This is odd, as device numbers are restricted to 000-177 octal,
   even though the bus is eight bits wide.  Otherwise, the interrupt flip-flops
   are standard.

   An IOCL command asserts Master Reset to the IMBA.

   Note that the IMBA does not respond to ROCL and so won't be seen when the IMB
   is polled to determine the installed channels.


   Implementation notes:

    1. Unlike most Series III interfaces, the IMBA is not ccontrolled by an I/O
       driver.  Instead, MPE automatically detects the presence of the IMBA at
       system startup, regardless of whether or not the GIC-connected devices
       are configured into the system.  Therefore, the IMBA is disabled by
       default and must be enabled explicitly when HP-IB devices are to be used.
*/



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



/* IMBA control word.

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

#define CN_MR               HP_BIT (0)          /* (M) master reset */

static const BITSET_NAME control_names [] = {   /* Control word names */
    "master reset"                              /*   bit  0 */
    };

static const BITSET_FORMAT control_format =     /* names, offset, direction, alternates, bar */
    { FMT_INIT (control_names, 15, msb_first, no_alt, no_bar) };


/* IMBA status word.

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

#define ST_SIO_OK           HP_BIT (0)          /* (S) SIO OK to use (CSRQ flip-flop is clear) */
#define ST_DIO_OK           HP_BIT (1)          /* (D) direct I/O OK to use (always 1) */
#define ST_POWER_OK         HP_BIT (2)          /* (V) +5 volts is present on the Intermodule Bus */

static const BITSET_NAME status_names [] = {    /* Device status word names */
    "SIO OK",                                   /*   bit  0 */
    "DIO OK",                                   /*   bit  1 */
    "Power OK"                                  /*   bit  2 */
    };

static const BITSET_FORMAT status_format =      /* names, offset, direction, alternates, bar */
    { FMT_INIT (status_names, 13, msb_first, no_alt, no_bar) };


/* IMBA state */

static FLIP_FLOP channel_request = CLEAR;       /* CSRQ flip-flop */
static HP_WORD   devno_response  = 0;           /* DEVNO response register */


/* IMBA local SCP support routines */

static IOP_INTERFACE imba_iop_interface;        /* interface to the IOP bus */
static IMB_INTERFACE imba_imb_interface;        /* interface to the Intermodule Bus */
static t_stat        imba_reset (DEVICE *dptr);


/* IMBA local utility routines */

static void master_reset (void);


/* IMBA SCP data structures */


/* Device information block */

static DIB imba_dib = {
    &imba_iop_interface,                        /* the device's IOP interface function pointer */
    NULL,                                       /* the execution initialization function pointer */
    IMBA_DEVNO,                                 /* the device number */
    SRNO_UNUSED,                                /* the service request number */
    12,                                         /* the interrupt priority */
    INTMASK_UNUSED,                             /* the interrupt mask */
    0,                                          /* the card index */
    CLEAR,                                      /* the interrupt request flip-flop */
    CLEAR,                                      /* the interrupt active flip-flop */
    FALSE,                                      /* TRUE if service has been requested */
    &imba_imb_interface,                        /* the device's IMB interface function pointer */
    1,                                          /* the IMB channel number */
    0                                           /* the IMB slot number */
    };


/* Unit list */

static UNIT imba_unit [] = {                    /* a dummy unit to satisfy SCP requirements */
    { UDATA (NULL, 0, 0) }
    };


/* Register list */

static REG imba_reg [] = {
/*    Macro   Name    Location          Width  Offset */
/*    ------  ------  ----------------  -----  ------ */
    { FLDATA (CSRQ,   channel_request,           0)    },
    { ORDATA (DEVNO,  devno_response,    8)            },

      DIB_REGS (imba_dib),

    { NULL }
    };


/* Modifier list */

static MTAB imba_mod [] = {
/*    Entry Flags  Value        Print String  Match String  Validation    Display        Descriptor         */
/*    -----------  -----------  ------------  ------------  ------------  -------------  ------------------ */
    { MTAB_XDV,    VAL_DEVNO,   "DEVNO",      NULL,         NULL,         &hp_show_dib,  (void *) &imba_dib },
    { MTAB_XDV,    VAL_INTPRI,  "INTPRI",     "INTPRI",     &hp_set_dib,  &hp_show_dib,  (void *) &imba_dib },

    { 0 }
    };


/* Debugging trace list.


   Implementation notes:

    1. The TRACE_DATA and TRACE_IMBUS flags are used indirectly by the mem_read
       and mem_write calls, and by the IMB command dispatcher.  Those routines
       check the operating device for the presence of the flags and report trace
       events on its behalf.
*/

static DEBTAB imba_deb [] = {
    { "CSRW",  TRACE_CSRW  },                   /* interface control and status actions */
    { "DATA",  TRACE_DATA  },                   /* I/O data accesses to memory (indirect) */
    { "IOBUS", TRACE_IOBUS },                   /* IOP interface bus signals and data words */
    { "IMBUS", TRACE_IMBUS },                   /* IMB signals and data words (indirect) */
    { NULL,    0           }
    };


/* Device descriptor */

DEVICE imba_dev = {
    "IMBA",                                     /* device name */
    imba_unit,                                  /* unit array */
    imba_reg,                                   /* register array */
    imba_mod,                                   /* modifier array */
    1,                                          /* number of units */
    10,                                         /* address radix */
    32,                                         /* address width = 4 GB */
    1,                                          /* address increment */
    8,                                          /* data radix */
    8,                                          /* data width */
    NULL,                                       /* examine routine */
    NULL,                                       /* deposit routine */
    &imba_reset,                                /* reset routine */
    NULL,                                       /* boot routine */
    NULL,                                       /* attach routine */
    NULL,                                       /* detach routine */
    &imba_dib,                                  /* device information block pointer */
    DEV_DISABLE | DEV_DIS | DEV_DEBUG,          /* device flags */
    0,                                          /* debug control flags */
    imba_deb,                                   /* debug flag name array */
    NULL,                                       /* memory size change routine */
    NULL                                        /* logical device name */
    };



/* IMB local SCP support routines */


/* Intermodule Bus Adapter IOP interface.

   The IMBA has a very simple interface to the Series III I/O Processor.  It
   responds only to the CIO, TIO, SIO, and RIO orders.  It does have interrupt
   capability.  Unusually, it responds to commands when the DEVNO lines present
   either 175 octal (the device number of the IMBA) or 200 octal + the contents
   of the DEVNO response register.  This allows it to masquerade as devices on
   the IMB that do not have direct interrupt interaction with the Series III.
   The register value is set in response to an IMB WIOC command.
*/

static SIGNALS_DATA imba_iop_interface (DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value)
{
INBOUND_SIGNAL signal;
INBOUND_SET    working_set      = inbound_signals;
HP_WORD        outbound_value   = 0;
OUTBOUND_SET   outbound_signals = 0;

tprintf (imba_dev, TRACE_IOBUS, "Received data %06o with signals %s\n",
                                inbound_value, fmt_bitset (inbound_signals, inbound_format));

while (working_set) {                                   /* while there are signals to process */
    signal = IONEXTSIG (working_set);                   /*   isolate the next signal */

    switch (signal) {                                   /* dispatch the I/O signal */

        case DCONTSTB:
            tprintf (imba_dev, TRACE_CSRW, "Control is %s\n",
                     fmt_bitset (inbound_value, control_format));

            if (inbound_value & CN_MR)                  /* if the master reset bit is set */
                master_reset ();                        /*   then reset the interface */
            break;


        case DSTATSTB:
            outbound_value = ST_DIO_OK | ST_POWER_OK;   /* set the "always asserted" bits */

            if (channel_request == CLEAR)               /* if a channel request is not in progress */
                outbound_value |= ST_SIO_OK;            /*   then an SIO order will be accepted */

            tprintf (imba_dev, TRACE_CSRW, "Status is %s\n",
                     fmt_bitset (outbound_value, status_format));
            break;


        case DSTARTIO:
            tprintf (imba_dev, TRACE_CSRW, "Channel program started\n");

            channel_request = SET;                      /* set the service request flip-flop */

            imb_assert_CSRQ (imba_dib.channel_number);  /* request channel service */
            break;


        case DRESETINT:
            dibptr->interrupt_active = CLEAR;           /* reset the interrupt active flip-flop */
            break;


        case INTPOLLIN:
            if (dibptr->interrupt_request) {            /* if an interrupt request is pending */
                dibptr->interrupt_request = CLEAR;      /*   then clear it */
                dibptr->interrupt_active  = SET;        /*     and mark it as now active */

                outbound_signals |= INTACK;             /* acknowledge the interrupt */
                outbound_value = devno_response;        /*   and return the masquerading device number */
                }

            else                                        /* otherwise the request has been reset */
                outbound_signals |= INTPOLLOUT;         /*   so let the IOP know to cancel it */
            break;


        case DSETINT:                                   /* not used by this interface */
        case DWRITESTB:                                 /* not used by this interface */
        case DREADSTB:                                  /* not used by this interface */
        case DSETMASK:                                  /* not used by this interface */

        case ACKSR:                                     /* not used by this interface */
        case TOGGLESR:                                  /* not used by this interface */
        case SETINT:                                    /* not used by this interface */
        case PCMD1:                                     /* not used by this interface */
        case PCONTSTB:                                  /* not used by this interface */
        case SETJMP:                                    /* not used by this interface */
        case PSTATSTB:                                  /* not used by this interface */
        case PWRITESTB:                                 /* not used by this interface */
        case PREADSTB:                                  /* not used by this interface */
        case EOT:                                       /* not used by this interface */
        case TOGGLEINXFER:                              /* not used by this interface */
        case TOGGLEOUTXFER:                             /* not used by this interface */
        case READNEXTWD:                                /* not used by this interface */
        case TOGGLESIOOK:                               /* not used by this interface */
        case DEVNODB:                                   /* not used by this interface */
        case XFERERROR:                                 /* not used by this interface */
        case CHANSO:                                    /* not used by this interface */
        case PFWARN:                                    /* not used by this interface */
            break;                                      /* signal is ignored */
        }

    working_set = working_set & ~signal;                /* remove current signal from set */
    }                                                   /*   and continue until all signals are processed */

tprintf (imba_dev, TRACE_IOBUS, "Returned data %06o with signals %s\n",
                                outbound_value, fmt_bitset (outbound_signals, outbound_format));

return IORETURN (outbound_signals, outbound_value);     /* return the outbound signals and data */
}


/* Intermodule Bus Adapter IMB interface.

   The IMBA also has a simple interface to the Starfish Intermodule Bus.  It
   responds to memory read and write commands, plus the SPOL1, WIOC, and IOCL
   commands.  Memory reads and writes are passed through a Port Controller to
   the Series III main memory.  The other IMB commands are handled here.
*/

static IMB_OUTBOUND imba_imb_interface (DIB *dibptr, IMB_INBOUND inbound)
{
const uint32   channel = dibptr->channel_number;
t_bool         success;
IMB_IO_COMMAND command;
IMB_OUTBOUND   outbound = { 0, ADN | DDN | PRO };

switch (inbound.opcode) {
    case IMB_Read:                                      /*  Memory Read */
        success = mem_read (&imba_dev, dma,             /*  issue a DMA read cycle */
                            inbound.address, &outbound.data);

        if (not success)                                /* if the read failed */
            outbound.signals &= ~DDN;                   /*   then deny the Data Done signal */
        break;


    case IMB_Write:                                     /*  Memory Write */
        success = mem_write (&imba_dev, dma,            /* issue a DMA write cycle */
                             inbound.address, inbound.data);

        if (not success)                                /* if the write failed */
            outbound.signals &= ~DDN;                   /*   then deny the Data Done signal */
        break;


    case IMB_IO_Write:                                  /*  I/O Write */
    case IMB_IO_Read:                                   /*  I/O Read */
        command  = IMB_COMMAND (inbound.address,        /* decode the I/O command */
                                inbound.opcode);

        switch (command) {                              /* dispatch the IMB I/O command */

            case SPOL1:                                 /* Service Poll 1 */
                if (channel_request == SET) {           /* if a request is in progress */
                    channel_request = CLEAR;            /*   then clear the request flip-flop */
                    outbound.data = HP_BIT (channel);   /*     and set our channel bit */
                    }
                break;


            case IOCL:                                  /* I/O Clear */
                master_reset ();                        /* master reset the hardware */
                break;


            case WIOC:                                  /* Write I/O Channel */
                devno_response = inbound.data;          /* save the device number for the masquerade */

                imba_dib.interrupt_request = SET;       /* set the interrupt request flip-flop */
                iop_assert_INTREQ (&imba_dib);          /*   and assert INTREQ */
                break;


            case RIOC:                                  /* not used by this interface */
            case OBII:                                  /* not used by this interface */
            case OBSI:                                  /* not used by this interface */
            case IPOLL:                                 /* not used by this interface */
            case ROCL:                                  /* not used by this interface */
            case SPOL2:                                 /* not used by this interface */
            case INIT:                                  /* not used by this interface */
            case SIOP:                                  /* not used by this interface */
            case HIOP:                                  /* not used by this interface */
            case SMSK:                                  /* not used by this interface */
                break;
            }


        break;

    case IMB_RW_Ones:                                   /* not used by this interface */
    case IMB_Memory:                                    /* not used by this interface */
        break;
    }

if (channel_request)                                    /* if a channel request is pending */
    outbound.signals |= CSRQ1;                          /*   then  assert CSRQ1 on the IMB */

return outbound;                                        /* return the outbound signals and data */
}


/* IMBA reset.

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

   For this interface, IORESET is identical to a Programmed Master Reset
   (control word bit 0 set).
*/

static t_stat imba_reset (DEVICE *dptr)
{
master_reset ();

return SCPE_OK;
}



/* IMBA local utility routines */


/* Programmed Master Reset.

   A programmed master reset occurs when a CIO order is issued with bit 0 set,
   by receipt of an IMB IOCL (I/O Clear) command, or by assertion of the IMB PON
   (Power On) signal.  It performs these actions:

     - clears the CSRQ flip-flop
     - clears the INTREQ flip-flop
     - clears the IMB handshake flip-flops
     - clears the IMB parity error flip-flop


   Implementation notes:

    1. In simulation, IMB handshake is implied, so the handshake flip-flops are
       not implemented.  Also, memory parity errors cannot occur, so the IMB
       parity error flip-flop is not implemented.
*/

static void master_reset (void)
{
channel_request = CLEAR;                                /* clear any channel service request */

imba_dib.interrupt_request = CLEAR;                     /* clear any current */
imba_dib.interrupt_active  = CLEAR;                     /*   interrupt request */

return;
}
