/* hp2100_pif.c: HP 12620A/12936A privileged interrupt fence simulator

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

   PIF          12620A/12936A privileged interrupt fence

   26-Jun-08    JDB     Rewrote device I/O to model backplane signals
   18-Jun-08    JDB     Created PIF device

   References:
   - 12620A Breadboard Interface Kit Operating and Service Manual
     (12620-90001, May-1978)
   - 12936A Privileged Interrupt Fence Accessory Installation and Service Manual
     (12936-90001, Mar-1974)


   The Privileged Interupt Fence (PIF) was used in DOS and RTE systems to
   provide privileged interrupt capability.  In non-privileged systems, DOS and
   RTE vectored all interrupts though the Central Interrupt Control (CIC)
   routine.  Within CIC, the interrupt system was turned off, the interrupt was
   categorized, the associated driver was identified and mapped into logical
   memory (if necessary), and the driver entered to handle the device service.
   When the driver exited, the interrupt system was turned on before returning
   to the point of interruption in the user's program.  In addition, the DOS and
   RTE operating systems themselves executed with the interrupt system off, as
   they were not reentrant.

   This process proved too lengthy for certain devices, which would lose
   interrupts or be forced to limit I/O speeds as a result.  To allow faster
   service, a driver could be written as a "privileged" driver and generated
   into a privileged system.  A privileged system operated with the interrupt
   system on when handling unprivileged device interrupts or executing within
   the operating system.  The PIF card was installed in the I/O backplane to
   separate privileged from unprivileged devices by controlling the interrupt
   priority chain signal (PRL) to lower-priority devices.  The privileged cards
   located below the fence were allowed to interrupt the service routines of the
   unprivileged cards that were located above the fence.

   When an unprivileged device interrupted, CIC would be entered as usual, and
   the interrupt system would be turned off.  However, after the system state
   was saved, the PIF would be configured to break the priority chain (deny
   PRL), so that subsequent interrupts from all unprivileged devices would be
   deferred.  Then the interrupt system would be turned on before normal CIC
   processing continued.  Interrupts from additional unprivileged devices would
   be held off by the PIF until the driver completed and CIC returned, just as
   in a non-privileged system.

   However, if a privileged device interrupted, the interrupt would be allowed,
   because the interrupt system was on, and the priority chain was intact for
   the devices below the fence.  A privileged device bypassed CIC and entered
   the associated device driver directly, and this would occur even if an
   unprivileged device driver or the operating system itself were executing.
   This provided very fast interrupt service time.

   HP produced two PIF cards: the 12936A Privileged Interrupt Fence Accessory
   for DOS, and the 12620A Breadboard Interface for RTE.  They behaved quite
   differently and were not interchangeable.

   The 12620A had the standard control and flag circuitry.  It behaved as most
   cards did; setting control and flag together lowered PRL and generated an
   interrupt.  The control and flag flip-flops were set and cleared with STC/CLC
   and STF/CLF instructions.  The SFS/SFC instructions could be used to test the
   flag state.

   The 12936A had a unique behavior.  Setting either control or flag lowered
   PRL.  An interrupt occurred when flag was set and control was clear.  The
   control flip-flop was controlled with STC/CLC.  The flag flip-flop was set
   with OTA/B and cleared with CLF.  SFC and SFS were not implemented and never
   skipped.
*/


#include "hp2100_defs.h"


/* Device flags */

#define DEV_V_12936     (DEV_V_UF + 0)                  /* 12936A card */

#define DEV_12936       (1 << DEV_V_12936)


/* PIF state variables */

FLIP_FLOP pif_control = CLEAR;                          /* control flip-flop */
FLIP_FLOP pif_flag    = CLEAR;                          /* flag flip-flop */
FLIP_FLOP pif_flagbuf = CLEAR;                          /* flag buffer flip-flop */


/* PIF global routines */

uint32 pif_io (uint32 select_code, IOSIG signal, uint32 data);

t_stat pif_reset     (DEVICE *dptr);
t_stat pif_set_card  (UNIT   *uptr, int32  val,  char  *cptr, void *desc);
t_stat pif_show_card (FILE   *st,   UNIT  *uptr, int32  val,  void *desc);


/* PIF data structures.

   pif_dib     PIF device information block
   pif_unit    PIF unit list
   pif_reg     PIF register list
   pif_mod     PIF modifier list
   pif_deb     PIF debug list
   pif_dev     PIF device descriptor

   Implementation note:

    1. The SIMH developer's manual says that a device's unit list may be NULL.
       However, if this is done, the register state cannot be examined or
       altered via SCP.  To work around this problem, we define a dummy unit
       that is not used otherwise.
*/

DEVICE pif_dev;

DIB pif_dib = { PIF, &pif_io };

UNIT pif_unit = {
    UDATA (NULL, 0, 0)                                  /* dummy unit */
    };

REG pif_reg [] = {
    { FLDATA (CTL,   pif_control,   0) },
    { FLDATA (FLG,   pif_flag,      0) },
    { FLDATA (FBF,   pif_flagbuf,   0) },
    { ORDATA (DEVNO, pif_dib.devno, 6), REG_HRO },
    { NULL }
    };

MTAB pif_mod [] = {
    { MTAB_XTD | MTAB_VDV, 0, NULL,    "12620A", &pif_set_card, NULL,           NULL     },
    { MTAB_XTD | MTAB_VDV, 1, NULL,    "12936A", &pif_set_card, NULL,           NULL     },
    { MTAB_XTD | MTAB_VDV, 0, "TYPE",  NULL,     NULL,          &pif_show_card, NULL     },
    { MTAB_XTD | MTAB_VDV, 0, "DEVNO", "DEVNO",  &hp_setdev,    &hp_showdev,    &pif_dev },

    { 0 }
    };

DEVICE pif_dev = {
    "PIF",                                  /* device name */
    &pif_unit,                              /* unit array */
    pif_reg,                                /* register array */
    pif_mod,                                /* modifier array */
    1,                                      /* number of units */
    10,                                     /* address radix */
    31,                                     /* address width */
    1,                                      /* address increment */
    8,                                      /* data radix */
    8,                                      /* data width */
    NULL,                                   /* examine routine */
    NULL,                                   /* deposit routine */
    &pif_reset,                             /* reset routine */
    NULL,                                   /* boot routine */
    NULL,                                   /* attach routine */
    NULL,                                   /* detach routine */
    &pif_dib,                               /* device information block */
    DEV_DEBUG | DEV_DISABLE,                /* device flags */
    0,                                      /* debug control flags */
    NULL,                                   /* debug flag name table */
    NULL,                                   /* memory size change routine */
    NULL };                                 /* logical device name */



/* I/O signal handler.

   Operation of the 12620A and the 12936A is different.  The I/O responses of
   the two cards are summarized below:

     Signal   12620A Action          12936A Action
     ------   --------------------   --------------------
     POPIO    Set FBF, FLG           Clear FBF, FLG
      CRS     Clear CTL              Clear CTL
      CLC     Clear CTL              Clear CTL
      STC     Set CTL                Set CTL
      CLF     Clear FBF, FLG         Clear FBF, FLG
      STF     Set FBF, FLG           none
      SFC     Skip if FLG clear      none
      SFS     Skip if FLG set        none
      IOI     none                   none
      IOO     none                   Set FBF, FLG
      PRL     ~(CTL * FLG)           ~(CTL + FLG)
      IRQ     CTL * FLG * FBF        ~CTL * FLG * FBF
      IAK     Clear FBF              Clear FBF
      SRQ     Follows FLG            Not driven

   Note that PRL and IRQ are non-standard for the 12936A.
*/

uint32 pif_io (uint32 select_code, IOSIG signal, uint32 data)
{
const char *hold_or_clear = (signal > ioCLF ? ",C" : "");
const t_bool is_rte_pif = (pif_dev.flags & DEV_12936) == 0;
const IOSIG base_signal = IOBASE (signal);              /* derive base signal */

switch (base_signal) {                                  /* dispatch base I/O signal */

    case ioCLF:                                         /* clear flag flip-flop */
        pif_flag = pif_flagbuf = CLEAR;                 /* clear flag buffer and flag */

        if (DEBUG_PRS (pif_dev))
            fputs (">>PIF: [CLF] Flag cleared\n", sim_deb);
        break;


    case ioSTF:                                         /* set flag flip-flop */
        if (is_rte_pif) {                               /* RTE PIF? */
            pif_flag = pif_flagbuf = SET;               /* set flag buffer and flag */

            if (DEBUG_PRS (pif_dev))
                fputs (">>PIF: [STF] Flag set\n", sim_deb);
            }
        break;


    case ioSFC:                                         /* skip if flag is clear */
        if (is_rte_pif)                                 /* RTE PIF? */
            setstdSKF (pif);                            /* card responds to SFC */
        break;


    case ioSFS:                                         /* skip if flag is set */
        if (is_rte_pif)                                 /* RTE PIF? */
            setstdSKF (pif);                            /* card responds to SFS */
        break;


    case ioIOO:                                         /* I/O data output */
        if (!is_rte_pif) {                              /* DOS PIF? */
            pif_flag = pif_flagbuf = SET;               /* set flag buffer and flag */
            pif_io (select_code, ioSIR, 0);             /* set IRQ (not normally done for IOO) */

            if (DEBUG_PRS (pif_dev))
                fprintf (sim_deb, ">>PIF: [OTx%s] Flag set\n", hold_or_clear);
            }
        break;


    case ioPOPIO:                                       /* power-on preset to I/O */
        pif_flag = pif_flagbuf =                        /* set or clear flag and flag buffer */
            (is_rte_pif ? SET : CLEAR);

        if (DEBUG_PRS (pif_dev))
            fprintf (sim_deb, ">>PIF: [POPIO] Flag %s\n",
                              (is_rte_pif ? "set" : "cleared"));
                                                        /* fall into CRS handler */

    case ioCRS:                                         /* control reset */
                                                        /* fall into CLC handler */

    case ioCLC:                                         /* clear control flip-flop */
        pif_control = CLEAR;                            /* clear control */

        if (DEBUG_PRS (pif_dev))
            fprintf (sim_deb, ">>PIF: [%s%s] Control cleared\n",
                              (signal == ioCRS ? "CRS" : "CLC"), hold_or_clear);
        break;


    case ioSTC:                                         /* set control flip-flop */
        pif_control = SET;                              /* set control */

        if (DEBUG_PRS (pif_dev))
            fprintf (sim_deb, ">>PIF: [STC%s] Control set\n", hold_or_clear);
        break;


    case ioSIR:                                         /* set interrupt request */
        if (is_rte_pif) {                               /* RTE PIF? */
            setstdPRL (select_code, pif);               /* set standard PRL signal */
            setstdIRQ (select_code, pif);               /* set standard IRQ signal */
            setstdSRQ (select_code, pif);               /* set standard SRQ signal */
            }

        else {                                          /* DOS PIF */
            setPRL (select_code, !(pif_control | pif_flag));
            setIRQ (select_code, !pif_control & pif_flag & pif_flagbuf);
            }

        if (DEBUG_PRS (pif_dev))
            fprintf (sim_deb, ">>PIF: [SIR] PRL = %d, IRQ = %d\n",
                              PRL (select_code), IRQ (select_code));
        break;


    case ioIAK:                                         /* interrupt acknowledge */
        pif_flagbuf = CLEAR;
        break;


    default:                                            /* all other signals */
        break;                                          /*   are ignored */
    }


if (signal > ioCLF)                                     /* multiple signals? */
    pif_io (select_code, ioCLF, 0);                     /* issue CLF */
else if (signal > ioSIR)                                /* signal affected interrupt status? */
    pif_io (select_code, ioSIR, 0);                     /* set interrupt request */

return data;
}


/* Simulator reset routine */

t_stat pif_reset (DEVICE *dptr)
{
pif_io (pif_dib.devno, ioPOPIO, 0);                     /* send POPIO signal */
return SCPE_OK;
}


/* Set card type.

   val == 0 --> set to 12936A (DOS PIF)
   val == 1 --> set to 12620A (RTE PIF)
*/

t_stat pif_set_card (UNIT *uptr, int32 val, char *cptr, void *desc)
{
if ((val < 0) || (val > 1) || (cptr != NULL))           /* sanity check */
    return SCPE_ARG;                                    /* bad argument */

if (val)                                                /* DOS PIF selected? */
    pif_dev.flags = pif_dev.flags | DEV_12936;          /* set to 12936A */
else                                                    /* RTE PIF selected */
    pif_dev.flags = pif_dev.flags & ~DEV_12936;         /* set to 12620A */

return SCPE_OK;
}


/* Show card type */

t_stat pif_show_card (FILE *st, UNIT *uptr, int32 val, void *desc)
{
if (pif_dev.flags & DEV_12936)
    fputs ("12936A", st);
else
    fputs ("12620A", st);

return SCPE_OK;
}
