/* sds_io.c: SDS 940 I/O simulator

   Copyright (c) 2001-2020, Robert M. Supnik

   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
   ROBERT M SUPNIK 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 Robert M Supnik shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from Robert M Supnik.

   01-Nov-2020  RMS     Fixed overrun/underrun handling in single-word IO
   23-Oct-2020  RMS     TOP disconnects the channel rather than setting CHF_EOR
   19-Mar-2012  RMS     Fixed various declarations (Mark Pizzolato)
*/

#include "sds_defs.h"

/* Data chain word */

#define CHD_INT         040                             /* int on chain */
#define CHD_PAGE        037                             /* new page # */

/* Interlace POT */

#define CHI_V_WC        14                              /* word count */
#define CHI_M_WC        01777
#define CHI_GETWC(x)    (((x) >> CHI_V_WC) & CHI_M_WC)
#define CHI_V_MA        0                               /* mem address */
#define CHI_M_MA        037777
#define CHI_GETMA(x)    (((x) >> CHI_V_MA) & CHI_M_MA)

/* System interrupt POT */

#define SYI_V_GRP       18                              /* group */
#define SYI_M_GRP       077
#define SYI_GETGRP(x)   (((x) >> SYI_V_GRP) & SYI_M_GRP)
#define SYI_DIS         (1 << 17)                       /* disarm if 0 */
#define SYI_ARM         (1 << 16)                       /* arm if 1 */
#define SYI_M_INT       0177777                         /* interrupt */

/* Pseudo-device number for EOM/SKS mode 3 */

#define I_GETDEV3(x)    ((((x) & 020046000) != 020046000)? ((x) & DEV_MASK): DEV_MASK)

#define TST_XFR(d,c)    (xfr_req & dev_map[d][c])
#define SET_XFR(d,c)    xfr_req = xfr_req | dev_map[d][c]
#define CLR_XFR(d,c)    xfr_req = xfr_req & ~dev_map[d][c]
#define INV_DEV(d,c)    (dev_dsp[d][c] == NULL)
#define VLD_DEV(d,c)    (dev_dsp[d][c] != NULL)
#define TST_EOR(c)      (chan_flag[c] & CHF_EOR)
#define QAILCE(a)       (((a) >= POT_ILCY) && ((a) < (POT_ILCY + NUM_CHAN)))

uint8 chan_uar[NUM_CHAN];                               /* unit addr */
uint16 chan_wcr[NUM_CHAN];                              /* word count */
uint16 chan_mar[NUM_CHAN];                              /* mem addr */
uint8 chan_dcr[NUM_CHAN];                               /* data chain */
uint32 chan_war[NUM_CHAN];                              /* word assembly */
uint8 chan_cpw[NUM_CHAN];                               /* char per word */
uint8 chan_cnt[NUM_CHAN];                               /* char count */
uint16 chan_mode[NUM_CHAN];                             /* mode */
uint16 chan_flag[NUM_CHAN];                             /* flags */
static const char *chname[NUM_CHAN] = {
    "W", "Y", "C", "D", "E", "F", "G", "H"
    };

extern uint32 M[MAXMEMSIZE];                            /* memory */
extern uint32 int_req;                                  /* int req */
extern uint32 xfr_req;                                  /* xfer req */
extern uint32 alert;                                    /* pin/pot alert */
extern uint32 X, EM2, EM3, OV, ion, bpt;
extern uint32 cpu_mode;
extern int32 rtc_pie;
extern int32 stop_invins, stop_invdev, stop_inviop;
extern uint32 mon_usr_trap;
extern UNIT cpu_unit;

t_stat chan_reset (DEVICE *dptr);
t_stat chan_read (int32 ch);
t_stat chan_write (int32 ch);
void chan_write_mem (int32 ch);
void chan_flush_war (int32 ch);
uint32 chan_mar_inc (int32 ch);
t_stat chan_eor (int32 ch);
t_stat pot_ilc (uint32 num, uint32 *dat);
t_stat pot_dcr (uint32 num, uint32 *dat);
t_stat pin_adr (uint32 num, uint32 *dat);
t_stat pot_fork (uint32 num, uint32 *dat);
t_stat dev_disc (uint32 ch, uint32 dev);
t_stat dev_wreor (uint32 ch, uint32 dev);
extern t_stat pot_RL1 (uint32 num, uint32 *dat);
extern t_stat pot_RL2 (uint32 num, uint32 *dat);
extern t_stat pot_RL4 (uint32 num, uint32 *dat);
extern t_stat pin_rads (uint32 num, uint32 *dat);
extern t_stat pot_rada (uint32 num, uint32 *dat);
extern t_stat pin_dsk (uint32 num, uint32 *dat);
extern t_stat pot_dsk (uint32 num, uint32 *dat);
t_stat pin_mux (uint32 num, uint32 *dat);
t_stat pot_mux (uint32 num, uint32 *dat);
extern void set_dyn_map (void);

/* SDS I/O model

   A device is modeled by its interactions with a channel.  Devices can only be
   accessed via channels.  Each channel has its own device address space.  This
   means devices can only be accessed from a specific channel.

   I/O operations start with a channel connect.  The EOM instruction is passed
   to the device via the conn routine.  This routine is also used for non-channel
   EOM's to the device.  For channel connects, the device must remember the
   channel number.

   The device responds (after a delay) by setting its XFR_RDY flag.  This causes
   the channel to invoke either the read or write routine (for input or output)
   to get or put the next character.  If the device is an asynchronous output
   device, it calls routine chan_set_ordy to see if there is output available.
   If there is, XFR_RDY is set; if not, the channel is marked to wake the
   attached device when output is available.  This prevents invalid rate errors.

   Output may be terminated by a write end of record, a disconnect, or both.
   Write end of record occurs when the word count reaches zero on an IORD or IORP
   operation.  It also occurs if a TOP instruction is issued.  The device is
   expected to respond by setting the end of record indicator in the channel,
   which will in turn trigger an end of record interrupt.

   When the channel operation completes, the channel disconnects and calls the
   disconnect processor to perform any device specific cleanup.  The differences
   between write end of record and disconnect are subtle.  On magtape output,
   for example, both signal end of record; but write end of record allows the
   magtape to continue moving, while disconnect halts its motion.

   Valid devices supply a routine to handle potentially all I/O operations
   (connect, disconnect, read, write, write end of record, sks).  There are
   separate routines for PIN and POT.

   Channels could, optionally, handle 12b or 24b characters.  The simulator can
   support all widths.
*/

t_stat chan_show_reg (FILE *st, UNIT *uptr, int32 val, CONST void *desc);

struct aldisp {
    t_stat      (*pin) (uint32 num, uint32 *dat);       /* altnum, *dat */
    t_stat      (*pot) (uint32 num, uint32 *dat);       /* altnum, *dat */
    };

/* Channel data structures

   chan_dev     channel device descriptor
   chan_unit    channel unit descriptor
   chan_reg     channel register list
*/

UNIT chan_unit = { UDATA (NULL, 0, 0) };

REG chan_reg[] = {
    { BRDATA (UAR, chan_uar, 8, 6, NUM_CHAN) },
    { BRDATA (WCR, chan_wcr, 8, 15, NUM_CHAN) },
    { BRDATA (MAR, chan_mar, 8, 16, NUM_CHAN) },
    { BRDATA (DCR, chan_dcr, 8, 6, NUM_CHAN) },
    { BRDATA (WAR, chan_war, 8, 24, NUM_CHAN) },
    { BRDATA (CPW, chan_cpw, 8, 2, NUM_CHAN) },
    { BRDATA (CNT, chan_cnt, 8, 3, NUM_CHAN) },
    { BRDATA (MODE, chan_mode, 8, 12, NUM_CHAN) },
    { BRDATA (FLAG, chan_flag, 8, CHF_N_FLG, NUM_CHAN) },
    { NULL }
    };

MTAB chan_mod[] = {
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, CHAN_W, "W", NULL,
      NULL, &chan_show_reg, NULL },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, CHAN_Y, "Y", NULL,
      NULL, &chan_show_reg, NULL },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, CHAN_C, "C", NULL,
      NULL, &chan_show_reg, NULL },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, CHAN_D, "D", NULL,
      NULL, &chan_show_reg, NULL },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, CHAN_E, "E", NULL,
      NULL, &chan_show_reg, NULL },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, CHAN_F, "F", NULL,
      NULL, &chan_show_reg, NULL },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, CHAN_G, "G", NULL,
      NULL, &chan_show_reg, NULL },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, CHAN_H, "H", NULL,
      NULL, &chan_show_reg, NULL }
    };

DEVICE chan_dev = {
    "CHAN", &chan_unit, chan_reg, chan_mod,
    1, 8, 8, 1, 8, 8,
    NULL, NULL, &chan_reset,
    NULL, NULL, NULL
    };

/* Tables */

static const uint32 int_zc[8] = {
    INT_WZWC, INT_YZWC, INT_CZWC, INT_DZWC,
    INT_EZWC, INT_FZWC, INT_GZWC, INT_HZWC
    };

static const uint32 int_er[8] = {
    INT_WEOR, INT_YEOR, INT_CEOR, INT_DEOR,
    INT_EEOR, INT_FEOR, INT_GEOR, INT_HEOR
    };

/* dev_map maps device and channel numbers to a transfer flag masks */

uint32 dev_map[64][NUM_CHAN];

/* dev_dsp maps device and channel numbers to dispatch routines */

t_stat (*dev_dsp[64][NUM_CHAN])(uint32 fnc, uint32 dev, uint32 *dat) = { {NULL} };

/* dev3_dsp maps system device numbers to dispatch routines */

t_stat (*dev3_dsp[64])(uint32 fnc, uint32 dev, uint32 *dat) = { NULL };

/* dev_alt maps alert numbers to dispatch routines */

struct aldisp dev_alt[] = {
    { NULL, NULL },
    { NULL, &pot_ilc }, { NULL, &pot_ilc },
    { NULL, &pot_ilc }, { NULL, &pot_ilc },
    { NULL, &pot_ilc }, { NULL, &pot_ilc },
    { NULL, &pot_ilc }, { NULL, &pot_ilc },
    { NULL, &pot_dcr }, { NULL, &pot_dcr },
    { NULL, &pot_dcr }, { NULL, &pot_dcr },
    { NULL, &pot_dcr }, { NULL, &pot_dcr },
    { NULL, &pot_dcr }, { NULL, &pot_dcr },
    { &pin_adr, NULL }, { &pin_adr, NULL },
    { &pin_adr, NULL }, { &pin_adr, NULL },
    { &pin_adr, NULL }, { &pin_adr, NULL },
    { &pin_adr, NULL }, { &pin_adr, NULL },
    { NULL, &pot_RL1 }, { NULL, &pot_RL2 },
    { NULL, &pot_RL4 },
    { &pin_rads, NULL }, { NULL, &pot_rada },
    { &pin_dsk, &pot_dsk }, { NULL, &pot_fork },
    { &pin_mux, &pot_mux }
    };

/* Single word I/O instructions */

t_stat op_wyim (uint32 inst, uint32 *dat)
{
int32 ch, dev;

ch = (inst & 000200000)? CHAN_W: CHAN_Y;                /* get chan# */
dev = chan_uar[ch] & DEV_MASK;                          /* get dev # */
if (chan_cnt[ch] <= chan_cpw[ch]) {                     /* buffer empty? */
    if (dev == 0)                                       /* no device? dead */
        return STOP_INVIOP;
    return STOP_IONRDY;                                 /* hang until full */
    }
*dat = chan_war[ch];                                    /* get data */
chan_war[ch] = 0;                                       /* reset war */
chan_cnt[ch] = 0;                                       /* reset cnt */
return SCPE_OK;
}

t_stat op_miwy (uint32 inst, uint32 dat)
{
int32 ch, dev;

ch = (inst & 000200000)? CHAN_W: CHAN_Y;                /* get chan# */
dev = chan_uar[ch] & DEV_MASK;                          /* get dev # */
if (chan_cnt[ch] != 0) {                                /* buffer full? */
    if (dev == 0)                                       /* no device? dead */
        return STOP_INVIOP;
    return STOP_IONRDY;                                 /* hang until full */
    }
chan_war[ch] = dat;                                     /* get data */
chan_cnt[ch] = chan_cpw[ch] + 1;                        /* buffer full */
if (chan_flag[ch] & CHF_OWAK) {                         /* output wake? */
    if (VLD_DEV (dev, ch))                              /* wake channel */
        SET_XFR (dev, ch);
    chan_flag[ch] = chan_flag[ch] & ~CHF_OWAK;          /* clear wake */
    }
return SCPE_OK;
}

t_stat op_pin (uint32 *dat)
{
uint32 al = alert;                                      /* local copy */

alert = 0;                                              /* clear alert */
if ((al == 0) || (dev_alt[al].pin == NULL))             /* inv alert? */
    CRETIOP;
return dev_alt[al].pin (al, dat);                       /* PIN from dev */
}

t_stat op_pot (uint32 dat)
{
uint32 al = alert;                                      /* local copy */

alert = 0;                                              /* clear alert */
if ((al == 0) || (dev_alt[al].pot == NULL))             /* inv alert? */
    CRETIOP;
return dev_alt[al].pot (al, &dat);                      /* POT to dev */
}

/* EOM/EOD */

t_stat op_eomd (uint32 inst)
{
uint32 mod = I_GETIOMD (inst);                          /* get mode */
uint32 ch = I_GETEOCH (inst);                           /* get chan # */
uint32 dev = inst & DEV_MASK;                           /* get dev # */
uint32 ch_dev = chan_uar[ch] & DEV_MASK;                /* chan curr dev # */
t_stat r;

switch (mod) {

    case 0:                                             /* IO control */
        if (dev) {                                      /* new dev? */
            if (ch_dev)                                 /* chan act? err */
                CRETIOP;
            if (INV_DEV (dev, ch))                      /* inv dev? err */
                CRETDEV;
            chan_war[ch] = chan_cnt[ch] = 0;            /* init chan */
            chan_dcr[ch] = 0;
            chan_uar[ch] = 0;
            if (!(chan_flag[ch] & CHF_ILCE) &&          /* ignore if ilc */
                !QAILCE (alert)) {                      /* already alerted */
                chan_flag[ch] = chan_mode[ch] = 0;
                if (ch >= CHAN_E)
                    chan_mode[ch] = CHM_CE;
                }
            if ((r = dev_dsp[dev][ch] (IO_CONN, inst, NULL)))/* connect */
                return r;
            if (!(chan_flag[ch] & CHF_ILCE) &&          /* ignore if ilc */
                !QAILCE (alert)) {                      /* already alerted */
                if ((inst & I_IND) || (ch >= CHAN_C)) { /* C-H? alert ilc */
                    alert = POT_ILCY + ch;
                    chan_mar[ch] = chan_wcr[ch] = 0;
                    }
                }
            if (chan_flag[ch] & CHF_24B)                /* 24B? 1 ch/wd */
                chan_cpw[ch] = 0;
            else if (chan_flag[ch] & CHF_12B)           /* 12B? 2 ch/wd */
                chan_cpw[ch] = CHC_GETCPW (inst) & 1;
            else chan_cpw[ch] = CHC_GETCPW (inst);      /* 6b, 1-4 ch/wd */
            chan_uar[ch] = dev;                         /* connected */
            if ((dev & DEV_OUT) && ion && !QAILCE (alert)) /* out, prog IO? */
                int_req = int_req | int_zc[ch];         /* initial intr */
            }
        else return dev_disc (ch, ch_dev);              /* disconnect */
        break;

    case 1:                                             /* buf control */
        if (QAILCE (alert)) {                           /* ilce alerted? */
            ch = alert - POT_ILCY;                      /* derive chan */
            if (ch >= CHAN_E)                           /* DACC? ext */
                inst = inst | CHM_CE;
            chan_mode[ch] = inst;                       /* save mode */
            chan_mar[ch] = (CHM_GETHMA (inst) << 14) |  /* get hi mar */
                (chan_mar[ch] & CHI_M_MA);
            chan_wcr[ch] = (CHM_GETHWC (inst) << 10) |  /* get hi wc */
                (chan_wcr[ch] & CHI_M_WC);
            }
        else if (dev) {                                 /* dev EOM */
            if (INV_DEV (dev, ch))                      /* inv dev? err */
                CRETDEV;
            return dev_dsp[dev][ch] (IO_EOM1, inst, NULL);
            }
        else {                                          /* chan EOM */
            inst = inst & 047677;
            if (inst == 040000) {                       /* alert ilce */
                alert = POT_ILCY + ch;
                chan_mar[ch] = chan_wcr[ch] = 0;
                }
            else if (inst == 002000)                    /* alert addr */
                alert = POT_ADRY + ch;
            else if (inst == 001000)                    /* alert DCR */
                alert = POT_DCRY + ch;
            else if (inst == 004000) {                  /* term output */
                if (ch_dev & DEV_OUT) {                 /* to output dev? */
                    if (chan_cnt[ch] || (chan_flag[ch] & CHF_ILCE)) /* busy, DMA? */
                        chan_flag[ch] = chan_flag[ch] | CHF_TOP;    /* TOP pending */
                    else return dev_disc (ch, ch_dev);  /* idle, disconnect */
                    }                                   /* end else TOP */
                else if (ch_dev & DEV_MT) {             /* change to scan? */
                    chan_uar[ch] = chan_uar[ch] | DEV_MTS;    /* change dev addr */
                    chan_flag[ch] = chan_flag[ch] | CHF_SCAN; /* set scan flag */
                    }                                   /* end else change scan */
                }                                       /* end else term output */
            }                                           /* end else chan EOM */
        break;

    case 2:                                             /* internal */
        if (ch >= CHAN_E) {                             /* EOD? */
            if (inst & 00300) {                         /* set EM? */
                if (inst & 00100)
                    EM2 = inst & 07;
                if (inst & 00200)
                    EM3 = (inst >> 3) & 07;
                set_dyn_map ();
                }
            break;
            }                                           /* end if EOD */
        if (inst & 00001)                               /* clr OV */
            OV = 0;
        if (inst & 00002)                               /* ion */
            ion = 1;
        else if (inst & 00004)                          /* iof */
            ion = 0;
        if ((inst & 00010) && (((X >> 1) ^ X) & EXPS))
            OV = 1;
        if (inst & 00020)                               /* alert sys int */
            alert = POT_SYSI;
        if (inst & 00100)                               /* arm clk pls */
            rtc_pie = 1;
        else if (inst & 00200)                          /* disarm pls */
            rtc_pie = 0;
        if ((inst & 01400) == 01400)                    /* alert RL4 */
            alert = POT_RL4;
        else if (inst & 00400)                          /* alert RL1 */
            alert = POT_RL1;
        else if (inst & 01000)                          /* alert RL2 */
            alert = POT_RL2;
        if (inst & 02000) {                             /* nml to mon */
            cpu_mode = MON_MODE;
            if (inst & 00400)
                mon_usr_trap = 1;
            }
        break;

    case 3:                                             /* special */
        dev = I_GETDEV3 (inst);                         /* special device */
        if (dev == DEV3_SMUX && !(cpu_unit.flags & UNIT_GENIE))
            dev = DEV3_GMUX;
        if (dev3_dsp[dev])                              /* defined? */
            return dev3_dsp[dev] (IO_CONN, inst, NULL);
        CRETINS;
        }                                               /* end case */

return SCPE_OK;
}

/* Skip if not signal */

t_stat op_sks (uint32 inst, uint32 *dat)
{
uint32 mod = I_GETIOMD (inst);                          /* get mode */
uint32 ch = I_GETSKCH (inst);                           /* get chan # */
uint32 dev = inst & DEV_MASK;                           /* get dev # */

*dat = 0;
if ((ch == 4) && !(inst & 037774)) {                    /* EM test */
    if (((inst & 0001) && (EM2 != 2)) ||
        ((inst & 0002) && (EM3 != 3)))
        *dat = 1;
    return SCPE_OK;
    }
switch (mod) {

    case 1:                                             /* ch, dev */
        if (dev) {                                      /* device */
            if (INV_DEV (dev, ch))                      /* inv dev? err */
                CRETDEV;
            dev_dsp[dev][ch] (IO_SKS, inst, dat);       /* do test */
            }
        else {                                          /* channel */
            if (((inst & 04000) && (chan_uar[ch] == 0)) ||
                ((inst & 02000) && (chan_wcr[ch] == 0)) ||
                ((inst & 01000) && ((chan_flag[ch] & CHF_ERR) == 0)) ||
                ((inst & 00400) && (chan_flag[ch] & CHF_IREC)))
                *dat = 1;
            }
        break;

    case 2:                                             /* internal test */
        if (inst & 0001) {                              /* test OV */
            *dat = OV ^ 1;                              /* skip if off */
            OV = 0;                                     /* and reset */
            break;
            }
        if (((inst & 00002) && !ion) ||                 /* ion, bpt test */
            ((inst & 00004) && ion) ||
            ((inst & 00010) && ((chan_flag[CHAN_W] & CHF_ERR) == 0)) ||
            ((inst & 00020) && ((chan_flag[CHAN_Y] & CHF_ERR) == 0)) ||
            ((inst & 00040) && ((bpt & 001) == 0)) ||
            ((inst & 00100) && ((bpt & 002) == 0)) ||
            ((inst & 00200) && ((bpt & 004) == 0)) ||
            ((inst & 00400) && ((bpt & 010) == 0)) ||
            ((inst & 01000) && (chan_uar[CHAN_W] == 0)) ||
            ((inst & 02000) && (chan_uar[CHAN_Y] == 0)))
            *dat = 1;
        break;

    case 3:                                             /* special */
        dev = I_GETDEV3 (inst);                         /* special device */
        if (dev == DEV3_SMUX && !(cpu_unit.flags & UNIT_GENIE))
            dev = DEV3_GMUX;
        if (dev3_dsp[dev])
            dev3_dsp[dev] (IO_SKS, inst, dat);
        else CRETINS;
        }                                               /* end case */

return SCPE_OK;
}

/* PIN/POT routines */

t_stat pot_ilc (uint32 num, uint32 *dat)
{
uint32 ch = num - POT_ILCY;

chan_mar[ch] = (chan_mar[ch] & ~CHI_M_MA) | CHI_GETMA (*dat);
chan_wcr[ch] = (chan_wcr[ch] & ~CHI_M_WC) | CHI_GETWC (*dat);
chan_flag[ch] = chan_flag[ch] | CHF_ILCE;
return SCPE_OK;
}

t_stat pot_dcr (uint32 num, uint32 *dat)
{
uint32 ch = num - POT_DCRY;

chan_dcr[ch] = (*dat) & (CHD_INT | CHD_PAGE);
chan_flag[ch] = chan_flag[ch] | CHF_DCHN;
return SCPE_OK;
}

t_stat pin_adr (uint32 num, uint32 *dat)
{
uint32 ch = num - POT_ADRY;

*dat = chan_mar[ch] & PAMASK;
return SCPE_OK;
}

/* System interrupt POT.

   The SDS 940 timesharing system uses a permanently asserted
   system interrupt as a way of forking the teletype input
   interrupt handler to a lower priority.  The interrupt is
   armed to set up the fork, and disarmed in the fork routine */

t_stat pot_fork (uint32 num, uint32 *dat)
{
uint32 igrp = SYI_GETGRP (*dat);                        /* get group */
uint32 fbit = (0100000 >> (VEC_FORK & 017));            /* bit in group */

if (igrp == ((VEC_FORK-0200) / 020)) {                  /* right group? */
    if ((*dat & SYI_ARM) && (*dat & fbit))              /* arm, bit set? */
        int_req = int_req | INT_FORK;
    if ((*dat & SYI_DIS) && !(*dat & fbit))             /* disarm, bit clr? */
        int_req = int_req & ~INT_FORK;
    }
return SCPE_OK;
}

/* Channel read invokes the I/O device to get the next character and,
   if not end of record, assembles it into the word assembly register.
   If the interlace is on, the full word is stored in memory.
   The key difference points for the various terminal functions are

        end of record   comp: EOT interrupt
                        IORD, IOSD: EOR interrupt, disconnect
                        IORP, IOSP: EOR interrupt, interrecord
        interlace off:  comp: EOW interrupt
                        IORD, IORP: ignore
                        IOSD, IOSP: overrun error
        --wcr == 0:     comp: clear interlace
                        IORD, IORP, IOSP: ZWC interrupt
                        IOSD: ZWC interrupt, EOR interrupt, disconnect

   Note that the channel can be disconnected if CHF_EOR is set, but must
   not be if XFR_REQ is set */

t_stat chan_read (int32 ch)
{
uint32 dat = 0;
uint32 dev = chan_uar[ch] & DEV_MASK;
uint32 tfnc = CHM_GETFNC (chan_mode[ch]);
t_stat r = SCPE_OK;

if ((dev != 0) && TST_XFR (dev, ch)) {                  /* ready to xfr? */
    if (INV_DEV (dev, ch))                              /* can't read? */
        CRETIOP;
    r = dev_dsp[dev][ch] (IO_READ, dev, &dat);          /* read data */
    if ((r != 0) || (chan_cnt[ch] > chan_cpw[ch]))      /* error or overrun? */
        chan_flag[ch] = chan_flag[ch] | CHF_ERR;
    else {                                              /* no, precess data */
        if (chan_flag[ch] & CHF_24B)                    /* 24B? */
            chan_war[ch] = dat;
        else if (chan_flag[ch] & CHF_12B)               /* 12B? */
            chan_war[ch] = ((chan_war[ch] << 12) | (dat & 07777)) & DMASK;
        else chan_war[ch] = ((chan_war[ch] << 6) | (dat & 077)) & DMASK;
        }
    if (chan_flag[ch] & CHF_SCAN)                       /* scanning? */
        chan_cnt[ch] = chan_cpw[ch];                    /* never full */
    else chan_cnt[ch] = chan_cnt[ch] + 1;               /* insert char */
    if (chan_cnt[ch] > chan_cpw[ch]) {                  /* full now? */
        if (chan_flag[ch] & CHF_ILCE) {                 /* interlace on? */
            chan_write_mem (ch);                        /* write to mem */
            if (chan_wcr[ch] == 0) {                    /* wc zero? */
                chan_flag[ch] = chan_flag[ch] & ~CHF_ILCE; /* clr interlace */
                if ((tfnc != CHM_COMP) && (chan_mode[ch] & CHM_ZC))
                    int_req = int_req | int_zc[ch];     /* zwc interrupt */
                if (tfnc == CHM_IOSD) {                 /* IOSD? also EOR */
                    if (chan_mode[ch] & CHM_ER)
                        int_req = int_req | int_er[ch];
                    dev_disc (ch, dev);                 /* disconnect */
                    }                                   /* end if IOSD */
                }                                       /* end if wcr == 0 */
            }                                           /* end if ilce on */
        else {                                          /* interlace off */
            if (TST_EOR (ch))                           /* eor? */
                return chan_eor (ch);
            if (tfnc == CHM_COMP) {                     /* C: EOW, intr */
                if (ion)
                    int_req = int_req | int_zc[ch];
                }
            else if (tfnc & CHM_SGNL)                   /* Sx: error */
                chan_flag[ch] = chan_flag[ch] | CHF_ERR;
            else chan_cnt[ch] = chan_cpw[ch];           /* Rx: ignore */
            }                                           /* end else ilce */
        }                                               /* end if full */
    }                                                   /* end if xfr */
if (TST_EOR (ch)) {                                     /* end record? */
    if (tfnc == CHM_COMP)                               /* C: fill war */
        chan_flush_war (ch);
    else if (chan_cnt[ch]) {                            /* RX, CX: fill? */
        chan_flush_war (ch);                            /* fill war */
        if (chan_flag[ch] & CHF_ILCE)                   /* ilce on? store */
            chan_write_mem (ch);
        }                                               /* end else if cnt */
    return chan_eor (ch);                               /* eot/eor int */
    }
return r;
}

void chan_write_mem (int32 ch)
{
WriteP (chan_mar[ch], chan_war[ch]);                    /* write to mem */
chan_mar[ch] = chan_mar_inc (ch);                       /* incr mar */
chan_wcr[ch] = (chan_wcr[ch] - 1) & 077777;             /* decr wcr */
chan_war[ch] = 0;                                       /* reset war */
chan_cnt[ch] = 0;                                       /* reset cnt */
return;
}

void chan_flush_war (int32 ch)
{
int32 i = (chan_cpw[ch] - chan_cnt[ch]) + 1;

if (i) {
    if (chan_flag[ch] & CHF_24B)
        chan_war[ch] = 0;
    else if (chan_flag[ch] & CHF_12B)
        chan_war[ch] = (chan_war[ch] << 12) & DMASK;
    else chan_war[ch] = (chan_war[ch] << (i * 6)) & DMASK;
    chan_cnt[ch] = chan_cpw[ch] + 1;
    }
return;
}

/* Channel write gets the next character and sends it to the I/O device.
   If this is the last character in an interlace operation, the end of
   record operation is invoked.
   The key difference points for the various terminal functions are

        end of record:  comp: EOT interrupt
                        IORD, IOSD: EOR interrupt, disconnect
                        IORP, IOSP: EOR interrupt, interrecord
        interlace off:  if not end of record, EOW interrupt
        --wcr == 0:     comp: EOT interrupt, disconnect
                        IORD, IORP: ignore
                        IOSD: ZWC interrupt, disconnect
                        IOSP: ZWC interrupt, interrecord
*/
t_stat chan_write (int32 ch)
{
uint32 dat = 0;
uint32 dev = chan_uar[ch] & DEV_MASK;
uint32 tfnc = CHM_GETFNC (chan_mode[ch]);
t_stat r = SCPE_OK;

if (dev && TST_XFR (dev, ch)) {                         /* ready to xfr? */
    if (INV_DEV (dev, ch))                              /* invalid dev? */
        CRETIOP;
    if (chan_cnt[ch] == 0) {                            /* buffer empty? */
        if (chan_flag[ch] & CHF_ILCE) {                 /* interlace on? */
            chan_war[ch] = ReadP (chan_mar[ch]);
            chan_mar[ch] = chan_mar_inc (ch);           /* incr mar */
            chan_wcr[ch] = (chan_wcr[ch] - 1) & 077777; /* decr mar */
            chan_cnt[ch] = chan_cpw[ch] + 1;            /* set cnt */
            }
        else {                                          /* ilce off */
             if (TST_EOR (dev))                         /* EOR? */
                return chan_eor (ch);
            chan_flag[ch] = chan_flag[ch] | CHF_ERR;    /* rate err */
            }                                           /* end else ilce */
        }                                               /* end if cnt */
    if (chan_cnt[ch] != 0) {                            /* if not underrun */
        chan_cnt[ch] = chan_cnt[ch] - 1;                /* decr cnt */
        if (chan_flag[ch] & CHF_24B)                    /* 24B? */
            dat = chan_war[ch];
        else if (chan_flag[ch] & CHF_12B) {             /* 12B? */
            dat = (chan_war[ch] >> 12) & 07777;         /* get halfword */
            chan_war[ch] = (chan_war[ch] << 12) & DMASK;/* remove from war */
            }
        else {                                          /* 6B */
            dat = (chan_war[ch] >> 18) & 077;           /* get char */
            chan_war[ch] = (chan_war[ch] << 6) & DMASK; /* remove from war */
            }
        }                                               /* end no underrun */
    r = dev_dsp[dev][ch] (IO_WRITE, dev, &dat);         /* write */
    if (r != 0)                                         /* error? */
        chan_flag[ch] = chan_flag[ch] | CHF_ERR;
    if (chan_cnt[ch] == 0) {                            /* buf empty? */
        if (chan_flag[ch] & CHF_ILCE) {                 /* ilce on? */
            if (chan_wcr[ch] == 0) {                    /* wc now 0? */
                chan_flag[ch] = chan_flag[ch] & ~CHF_ILCE; /* ilc off */
                if (tfnc == CHM_COMP) {                 /* compatible? */
                    if (ion)
                        int_req = int_req | int_zc[ch];
                    dev_disc (ch, dev);                 /* disconnnect */
                    }                                   /* end if comp */
                else {                                  /* extended */
                    if (chan_mode[ch] & CHM_ZC)         /* ZWC int */
                        int_req = int_req | int_zc[ch];
                    if (tfnc == CHM_IOSD) {             /* SD */
                        if (chan_mode[ch] & CHM_ER)     /* EOR int */
                            int_req = int_req | int_er[ch];
                        dev_disc (ch, dev);             /* disconnnect */
                        }                               /* end if SD */
                    else if (!(tfnc && CHM_SGNL) ||     /* IORx or IOSP TOP? */
                        (chan_flag[ch] & CHF_TOP))
                        dev_disc (ch, dev);             /* R: disconnect */
                    chan_flag[ch] = chan_flag[ch] & ~CHF_TOP;
                    }                                   /* end else comp */
                }                                       /* end if wcr */
            }                                           /* end if ilce */
        else if (chan_flag[ch] & CHF_TOP) {             /* off, TOP pending? */
            chan_flag[ch] = chan_flag[ch] & ~CHF_TOP;   /* clear TOP */
            dev_disc (ch, dev);                         /* disconnect */
            }
        else if (ion)                                   /* no TOP, EOW intr */
            int_req = int_req | int_zc[ch];
        }                                               /* end if cnt */
   }                                                    /* end if xfr */
if (TST_EOR (ch))                                       /* eor rcvd? */
    return chan_eor (ch);
return r;
}

/* MAR increment */

uint32 chan_mar_inc (int32 ch)
{
uint32 t = (chan_mar[ch] + 1) & PAMASK;                 /* incr mar */

if ((chan_flag[ch] & CHF_DCHN) && ((t & VA_POFF) == 0)) { /* chain? */
    chan_flag[ch] = chan_flag[ch] & ~CHF_DCHN;          /* clr flag */
    if (chan_dcr[ch] & CHD_INT)                         /* if armed, intr */
        int_req = int_req | int_zc[ch];
    t = (chan_dcr[ch] & CHD_PAGE) << VA_V_PN;           /* new mar */
    }
return t;
}

/* End of record action */

t_stat chan_eor (int32 ch)
{
uint32 tfnc = CHM_GETFNC (chan_mode[ch]);
uint32 dev = chan_uar[ch] & DEV_MASK;

chan_flag[ch] = chan_flag[ch] & ~(CHF_EOR | CHF_ILCE);  /* clr eor, ilce */
if (((tfnc == CHM_COMP) && ion) || (chan_mode[ch] & CHM_ER))
    int_req = int_req | int_er[ch];                     /* EOT/EOR? */
if (dev && (tfnc & CHM_PROC))                           /* P, still conn? */
    chan_flag[ch] = chan_flag[ch] | CHF_IREC;           /* interrecord */
else return dev_disc (ch, dev);                         /* disconnect */
return SCPE_OK;
}

/* Utility routines */

t_stat dev_disc (uint32 ch, uint32 dev)
{
chan_uar[ch] = 0;                                       /* disconnect */
if (dev_dsp[dev][ch])
    return dev_dsp[dev][ch] (IO_DISC, dev, NULL);
return SCPE_OK;
}

t_stat dev_wreor (uint32 ch, uint32 dev)
{
if (dev_dsp[dev][ch])
    return dev_dsp[dev][ch] (IO_WREOR, dev, NULL);
chan_flag[ch] = chan_flag[ch] | CHF_EOR;                /* set eor */
return SCPE_OK;
}

/* Externally visible routines */
/* Channel driver */

t_stat chan_process (void)
{
int32 i, dev;
t_stat r;

for (i = 0; i < NUM_CHAN; i++) {                        /* loop thru */
    dev = chan_uar[i] & DEV_MASK;                       /* get dev */
    if ((dev && TST_XFR (dev, i)) || TST_EOR (i)) {     /* chan active? */
        if (dev & DEV_OUT)                              /* write */
            r = chan_write (i);
        else r = chan_read (i);                         /* read */
        if (r)
            return r;
        }
    }
return SCPE_OK;
}

/* Test for channel active */

t_bool chan_testact (void)
{
int32 i, dev;

for (i = 0; i < NUM_CHAN; i++) {
    dev = chan_uar[i] & DEV_MASK;
    if ((dev && TST_XFR (dev, i)) || TST_EOR (i))
        return 1;
    }
return 0;
}

/* Async output device ready for more data */

void chan_set_ordy (int32 ch)
{
if ((ch >= 0) && (ch < NUM_CHAN)) {
    int32 dev = chan_uar[ch] & DEV_MASK;                /* get dev */
    if (chan_cnt[ch] || (chan_flag[ch] & CHF_ILCE))     /* buf or ilce? */
        SET_XFR (dev, ch);                              /* set xfr flg */
    else chan_flag[ch] = chan_flag[ch] | CHF_OWAK;      /* need wakeup */
    }
return;
}

/* Set flag in channel */

void chan_set_flag (int32 ch, uint32 fl)
{
if ((ch >= 0) && (ch < NUM_CHAN))
    chan_flag[ch] = chan_flag[ch] | fl;
return;
}

/* Set UAR in channel */

void chan_set_uar (int32 ch, uint32 dev)
{
if ((ch >= 0) && (ch < NUM_CHAN))
    chan_uar[ch] = dev & DEV_MASK;
return;
}

/* Disconnect channel */

void chan_disc (int32 ch)
{
if ((ch >= 0) && (ch < NUM_CHAN))
    chan_uar[ch] = 0;
return;
}

/* Reset channels */

t_stat chan_reset (DEVICE *dptr)
{
int32 i;

xfr_req = 0;
for (i = 0; i < NUM_CHAN; i++) {
    chan_uar[i] = 0;
    chan_wcr[i] = 0;
    chan_mar[i] = 0;
    chan_dcr[i] = 0;
    chan_war[i] = 0;
    chan_cpw[i] = 0;
    chan_cnt[i] = 0;
    chan_mode[i] = 0;
    chan_flag[i] = 0;
    }
return SCPE_OK;
}

/* Channel assignment routines */

t_stat set_chan (UNIT *uptr, int32 val, CONST char *sptr, void *desc)
{
DEVICE *dptr;
DIB *dibp;
int32 i;

if (sptr == NULL)                                        /* valid args? */
    return SCPE_ARG;
if (uptr == NULL)
    return SCPE_IERR;
dptr = find_dev_from_unit (uptr);
if (dptr == NULL)
    return SCPE_IERR;
dibp = (DIB *) dptr->ctxt;
if (dibp == NULL)
    return SCPE_IERR;
for (i = 0; i < NUM_CHAN; i++) {                        /* match input */
    if (strcmp (sptr, chname[i]) == 0) {                /* find string */
        if (val && !(val & (1 << i)))                   /* legal? */
            return SCPE_ARG;
        dibp->chan = i;                                 /* store new */
        return SCPE_OK;
        }
    }
return SCPE_ARG;
}

t_stat show_chan (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
DEVICE *dptr;
DIB *dibp;

if (uptr == NULL)
    return SCPE_IERR;
dptr = find_dev_from_unit (uptr);
if (dptr == NULL)
    return SCPE_IERR;
dibp = (DIB *) dptr->ctxt;
if (dibp == NULL)
    return SCPE_IERR;
fprintf (st, "channel=%s", chname[dibp->chan]);
return SCPE_OK;
}

/* Init device tables */

t_bool io_init (void)
{
DEVICE *dptr;
DIB *dibp;
DSPT *tplp;
int32 ch;
uint32 i, j, dev, doff;

/* Clear dispatch table, device map */

for (i = 0; i < NUM_CHAN; i++) {
    for (j = 0; j < (DEV_MASK + 1); j++) {
        dev_dsp[j][i] = NULL;
        dev_map[j][i] = 0;
        }
    }

/* Test each device for conflict; add to map; init tables */

for (i = 0; (dptr = sim_devices[i]); i++) {             /* loop thru devices */
    dibp = (DIB *) dptr->ctxt;                          /* get DIB */
    if ((dibp == NULL) || (dptr->flags & DEV_DIS))      /* exist, enabled? */
        continue;
    ch = dibp->chan;                                    /* get channel */
    dev = dibp->dev;                                    /* get device num */
    if (ch < 0)                                         /* special device */
        dev3_dsp[dev] = dibp->iop;
    else {
        if (dibp->tplt == NULL)                         /* must have template */
            return TRUE;
        for (tplp = dibp->tplt; tplp->num; tplp++) {    /* loop thru templates */
            for (j = 0; j < tplp->num; j++) {           /* repeat as needed */
                doff = dev + tplp->off + j;             /* get offset dnum */
                if (dev_map[doff][ch]) {                /* slot in use? */
                    sim_printf ("Device number conflict, chan = %s, devno = %02o\n",
                                chname[ch], doff);
                    return TRUE;
                    }
                dev_map[doff][ch] = dibp->xfr;          /* set xfr flag */
                dev_dsp[doff][ch] = dibp->iop;          /* set dispatch */
                }                                       /* end for j */
            }                                           /* end for tplt */
        }                                               /* end else */
    }                                                   /* end for i */
return FALSE;
}

/* Display channel state */

t_stat chan_show_reg (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
if ((val < 0) || (val >= NUM_CHAN)) return SCPE_IERR;
fprintf (st, "UAR:      %02o\n", chan_uar[val]);
fprintf (st, "WCR:      %05o\n", chan_wcr[val]);
fprintf (st, "MAR:      %06o\n", chan_mar[val]);
fprintf (st, "DCR:      %02o\n", chan_dcr[val]);
fprintf (st, "WAR:      %08o\n", chan_war[val]);
fprintf (st, "CPW:      %o\n", chan_cpw[val]);
fprintf (st, "CNT:      %o\n", chan_cnt[val]);
fprintf (st, "MODE:     %03o\n", chan_mode[val]);
fprintf (st, "FLAG:     %04o\n", chan_flag[val]);
return SCPE_OK;
}
