/* i7094_com.c: IBM 7094 7750 communications interface simulator

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

   com          7750 controller
   coml         7750 lines

   29-Sep-22    RMS     Changed structures to arrays for display
   07-Apr-17    RMS     Fixed typo in accessing unit array (COVERITY)
   12-Aug-10    RMS     Major rewrite for CTSS (Dave Pitts)
   19-Nov-08    RMS     Revised for common TMXR show routines

   This module implements an abstract simulator for the IBM 7750 communications
   computer as used by the CTSS system.  The 7750 supports up to 112 lines;
   the simulator supports 33.  The 7750 can handle both high-speed lines, in
   6b and 12b mode, and normal terminals, in 12b mode only; the simulator 
   supports only terminals.  The 7750 can handle many different kinds of
   terminals; the simulator supports only a limited subset.

   Input is asynchronous and line buffered.  When valid input (a line or a
   control message) is available, the 7750 sets ATN1 to signal availability of
   input.  When the 7094 issues a CTLRN, the 7750 gathers available input characters
   into a message.  The message has a 12b sequence number, followed by 12b line
   number/character pairs, followed by end-of-medium (03777).  Input characters
   can either be control characters (bit 02000 set) or data characters.  Data
   characters are 1's complemented and are 8b wide: 7 data bits and 1 parity
   bit (which may be 0).

   Output is synchronous.  When the 7094 issues a CTLWN, the 7750 interprets
   the channel output as a message.  The message has a 12b line number, followed
   by a 12b character count, followed by characters, followed by end-of-medium.
   If bit 02000 of the line number is set, the characters are 12b wide.  If
   bit 01000 is set, the message is a control message.  12b characters consist
   of 7 data bits, 1 parity bit, and 1 start bit.  Data characters are 1's
   complemented.  Data character 03777 is special and causes the 7750 to
   repeat the previous bit for the number of bit times specified in the next
   character.  This is used to generate delays for positioning characters.

   The 7750 supports flow control for output.  To help the 7094 account for
   usage of 7750 buffer memory, the 7750 sends 'character output completion'
   messages for every 'n' characters output on a line, where n <= 31.

   Note that the simulator console is mapped in as line n+1.
*/

#include "i7094_defs.h"
#include "sim_sock.h"
#include "sim_tmxr.h"
#include <ctype.h>

#define COM_MLINES      31                              /* mux lines */
#define COM_TLINES      (COM_MLINES + 1)                /* total lines */
#define COM_BUFSIZ      120                             /* max chan transfer */
#define COM_PKTSIZ      16384                           /* character buffer */

#define UNIT_V_2741     (TTUF_V_UF + 0)                 /* 2741 - ni */
#define UNIT_V_K35      (TTUF_V_UF + 1)                 /* KSR-35 */
#define UNIT_2741       (1 << UNIT_V_2741)
#define UNIT_K35        (1 << UNIT_V_K35)

#define CONN            u3                              /* line is connected */
#define NEEDID          u4                              /* need to send ID */
#define NOECHO          u5                              /* no echo */
#define INPP            u6                              /* input pending */

#define COM_INIT_POLL   8000                            /* polling interval */
#define COMC_WAIT       2                               /* channel delay time */
#define COML_WAIT       1000                            /* char delay time */
#define COM_LBASE       4                               /* start of lines */

/* Input threads */

#define COM_PLU         0                               /* multiplexor poll */
#define COM_CIU         1                               /* console input */
#define COM_CHU         2                               /* channel transfer */
#define COM_SNS         3                               /* sense transfer */

/* Communications input */

#define COMI_VALIDL     02000                           /* valid line flag */
#define COMI_PARITY     00200                           /* parity bit */
#define COMI_DIALUP     02001                           /* dialup */
#define COMI_ENDID      02002                           /* end ID */
#define COMI_INTR       02003                           /* interrupt */
#define COMI_QUIT       02004                           /* quit */
#define COMI_HANGUP     02005                           /* hangup */
#define COMI_EOM        03777                           /* end of medium */
#define COMI_COMP(x)    ((uint16) (03000 + ((x) & COMI_CMAX)))
#define COMI_K35        1                               /* KSR-35 ID */
#define COMI_K37        7                               /* KSR-37 ID */
#define COMI_2741       8                               /* 2741 ID */
#define COMI_CMAX       31                              /* max chars returned */
#define COMI_BMAX       50                              /* buffer max, words */
#define COMI_12BMAX     ((3 * COMI_BMAX) - 1)           /* last 12b char */

/* Communications output */

#define COMO_LIN12B     INT64_C(0200000000000)          /* line is 12b */
#define COMO_LINCTL     INT64_C(0100000000000)          /* control msg */
#define COMO_GETLN(x)   (((uint32) ((x) >> 24)) & 0777)
#define COMO_CTLRST     00000                           /* control reset */
#define COMO_BITRPT     03777                           /* bit repeat */
#define COMO_EOM12B     07777                           /* end of medium */
#define COMO_BMAX       94                              /* buffer max, words */
#define COMO_12BMAX     ((3 * COMO_BMAX) - 1)

/* Status word (60b) */

#define COMS_PCHK       INT64_C(004000000000000000000)  /* prog check */
#define COMS_DCHK       INT64_C(002000000000000000000)  /* data check */
#define COMS_EXCC       INT64_C(001000000000000000000)  /* exc cond */
#define COMS_MLNT       INT64_C(000040000000000000000)  /* message length check */
#define COMS_CHNH       INT64_C(000020000000000000000)  /* channel hold */
#define COMS_CHNQ       INT64_C(000010000000000000000)  /* channel queue full */
#define COMS_ITMO       INT64_C(000000100000000000000)  /* interface timeout */
#define COMS_DATR       INT64_C(000000004000000000000)  /* data message ready */
#define COMS_INBF       INT64_C(000000002000000000000)  /* input buffer free */
#define COMS_SVCR       INT64_C(000000001000000000000)  /* service message ready */
#define COMS_PALL       INT64_C(000000000000000000000)
#define COMS_DALL       INT64_C(000000000000000000000)
#define COMS_EALL       INT64_C(000000000000000000000)
#define COMS_DYN        INT64_C(000000007000000000000)

/* Report variables */

#define COMR_FQ         1                               /* free queue */
#define COMR_IQ         2                               /* input queue */
#define COMR_OQ         4                               /* output queue */

/* List heads and entries */

#define HEAD            0
#define TAIL            1

#define NEXT            0
#define DATA            1

/* The 7750 character buffer is maintained as linked lists.  The lists are:

   free                 free list
   inpq[ln]             input queue for line n
   outq[ln]             output queue for line ln

   Links are done as subscripts in array com_pkt.  This allows the list
   headers and the queues themselves to be saved and restored. */

uint32 com_ch = CH_E;                                   /* saved channel */
uint32 com_enab = 0;                                    /* 7750 enabled */
uint32 com_msgn = 0;                                    /* next input msg num */
uint32 com_sta = 0;                                     /* 7750 state */
uint32 com_stop = 0;                                    /* channel stop */
uint32 com_quit = 003;                                  /* quit code */
uint32 com_intr = 0;                                    /* interrupt code */
uint32 com_bptr = 0;                                    /* buffer pointer */
uint32 com_blim = 0;                                    /* buffer count */
uint32 com_tps = 50;                                    /* polls/second */
t_uint64 com_sns = 0;                                   /* sense word */
t_uint64 com_chob = 0;                                  /* chan output buf */
uint32 com_chob_v = 0;                                  /* valid flag */
t_uint64 com_buf[COM_BUFSIZ];                           /* channel buffer */
uint16 com_free[2];                                     /* free list */
uint32 com_not_ret[COM_TLINES] = { 0 };                 /* chars not returned */
uint16 com_inpq[COM_TLINES][2] = { {0} };               /* input queues */
uint16 com_outq[COM_TLINES][2] = { {0} };               /* output queues */
uint16 com_pkt[COM_PKTSIZ][2];                          /* character packets */
TMLN com_ldsc[COM_MLINES] = { {0} };                    /* line descriptors */
TMXR com_desc = { COM_MLINES, 0, 0, com_ldsc };         /* mux descriptor */

/* Even parity truth table */

static const uint8 com_epar[128] = {
    0, 1, 1, 0, 1, 0, 0, 1,
    1, 0, 0, 1, 0, 1, 1, 0,
    1, 0, 0, 1, 0, 1, 1, 0,
    0, 1, 1, 0, 1, 0, 0, 1,
    1, 0, 0, 1, 0, 1, 1, 0,
    0, 1, 1, 0, 1, 0, 0, 1,
    0, 1, 1, 0, 1, 0, 0, 1,
    1, 0, 0, 1, 0, 1, 1, 0,
    1, 0, 0, 1, 0, 1, 1, 0,
    0, 1, 1, 0, 1, 0, 0, 1,
    0, 1, 1, 0, 1, 0, 0, 1,
    1, 0, 0, 1, 0, 1, 1, 0,
    0, 1, 1, 0, 1, 0, 0, 1,
    1, 0, 0, 1, 0, 1, 1, 0,
    1, 0, 0, 1, 0, 1, 1, 0,
    0, 1, 1, 0, 1, 0, 0, 1
    };

extern uint32 ch_req;

t_stat com_chsel (uint32 ch, uint32 sel, uint32 unit);
t_stat com_chwr (uint32 ch, t_uint64 val, uint32 flags);
t_stat comi_svc (UNIT *uptr);
t_stat comc_svc (UNIT *uptr);
t_stat como_svc (UNIT *uptr);
t_stat coms_svc (UNIT *uptr);
t_stat comti_svc (UNIT *uptr);
t_stat comto_svc (UNIT *uptr);
t_stat com_reset (DEVICE *dptr);
t_stat com_attach (UNIT *uptr, CONST char *cptr);
t_stat com_detach (UNIT *uptr);
t_stat com_show_ctrl (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat com_show_freeq (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat com_show_allq (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat com_show_oneq (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
void com_reset_ln (uint32 i);
uint16 com_get_nexti (uint32 *ln);
uint16 com_gethd_free (uint16 *lh);
uint16 com_gethd (uint16 *lh);
uint16 com_gettl_free (uint16 *lh);
uint16 com_gettl (uint16 *lh);
t_bool com_new_puttl (uint16 *lh, uint16 val);
void com_puttl (uint16 *lh, uint16 ent);
t_bool com_test_inp (void);
void com_set_inpp (uint32 ln);
t_uint64 com_getob (uint32 ch);
t_bool com_qdone (uint32 ch);
void com_end (uint32 ch, uint32 fl, uint32 st);
t_stat com_send_id (uint32 ln);
uint32 com_gen_ccmp (uint32 ln);
t_bool com_queue_in (uint32 ln, uint32 ch);
uint32 com_queue_out (uint32 ln, uint32 *c1);
void com_set_sns (t_uint64 stat);

/* COM data structures

   com_dev      COM device descriptor
   com_unit     COM unit descriptor
   com_reg      COM register list
   com_mod      COM modifiers list
*/

DIB com_dib = { &com_chsel, &com_chwr };

UNIT com_unit[] = {
    { UDATA (&comi_svc, UNIT_ATTABLE, 0), COM_INIT_POLL },
    { UDATA (&comti_svc, UNIT_DIS, 0), KBD_POLL_WAIT },
    { UDATA (&comc_svc, UNIT_DIS, 0), COMC_WAIT },
    { UDATA (&coms_svc, UNIT_DIS, 0), COMC_WAIT }
    };

UNIT coml_unit[] = {
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&como_svc, 0, 0), COML_WAIT },
    { UDATA (&comto_svc, 0, 0), COML_WAIT },
    };

REG com_reg[] = {
    { FLDATA (ENABLE, com_enab, 0) },
    { ORDATA (STATE, com_sta, 6) },
    { ORDATA (MSGNUM, com_msgn, 12) },
    { ORDATA (SNS, com_sns, 60) },
    { ORDATA (CHOB, com_chob, 36) },
    { FLDATA (CHOBV, com_chob_v, 0) },
    { FLDATA (STOP, com_stop, 0) },
    { ORDATA (QUIT, com_quit, 7) },
    { ORDATA (INTR, com_intr, 7) },
    { BRDATA (BUF, com_buf, 8, 36, COM_BUFSIZ) },
    { DRDATA (BPTR, com_bptr, 7), REG_RO },
    { DRDATA (BLIM, com_blim, 7), REG_RO },
    { BRDATA (NRET, com_not_ret, 10, 32, COM_TLINES), REG_RO + PV_LEFT },
    { URDATA (NEEDID, coml_unit[0].NEEDID, 8, 1, 0, COM_TLINES, 0) },
    { URDATA (NOECHO, coml_unit[0].NOECHO, 8, 1, 0, COM_TLINES, 0) },
    { URDATA (INPP, coml_unit[0].INPP, 8, 1, 0, COM_TLINES, 0) },
    { VBRDATA (FREEQ, com_free, 10, 16, 2) },
    { VBRDATA (INPQ, com_inpq, 10, 16, 2 * COM_TLINES) },
    { VBRDATA (OUTQ, com_outq, 10, 16, 2 * COM_TLINES) },
    { VBRDATA (PKTB, com_pkt, 10, 16, 2 * COM_PKTSIZ) },
    { DRDATA (TTIME, com_unit[COM_CIU].wait, 24), REG_NZ + PV_LEFT },
    { DRDATA (WTIME, com_unit[COM_CHU].wait, 24), REG_NZ + PV_LEFT },
    { DRDATA (CHAN, com_ch, 3), REG_HRO },
    { NULL }
    };

MTAB com_mod[] = {
    { UNIT_ATT, UNIT_ATT, "summary", NULL,
      NULL, &tmxr_show_summ, (void *) &com_desc },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "CONNECTIONS", NULL,
      NULL, &tmxr_show_cstat, (void *) &com_desc },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "STATISTICS", NULL,
      NULL, &tmxr_show_cstat, (void *) &com_desc },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, COMR_FQ, "FREEQ", NULL,
      NULL, &com_show_ctrl, 0 },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, COMR_IQ, "INPQ", NULL,
      NULL, &com_show_ctrl, 0 },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, COMR_OQ, "OUTQ", NULL,
      NULL, &com_show_ctrl, 0 },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0xFFFFFFFF, "ALL", NULL,
      NULL, &com_show_ctrl, 0 },
    { 0 }
    };

DEVICE com_dev = {
    "COM", com_unit, com_reg, com_mod,
    3, 10, 31, 1, 16, 8,
    &tmxr_ex, &tmxr_dep, &com_reset,
    NULL, &com_attach, &com_detach,
    &com_dib, DEV_MUX | DEV_DIS
    };

/* COML data structures

   coml_dev     COML device descriptor
   coml_unit    COML unit descriptor
   coml_reg     COML register list
   coml_mod     COML modifiers list
*/

MTAB coml_mod[] = {
    { UNIT_K35+UNIT_2741, 0        , "KSR-37", "KSR-37", NULL },
    { UNIT_K35+UNIT_2741, UNIT_K35 , "KSR-35", "KSR-35", NULL },
//  { UNIT_K35+UNIT_2741, UNIT_2741, "2741",   "2741", NULL },
    { MTAB_XTD|MTAB_VUN, 0, NULL, "DISCONNECT",
      &tmxr_dscln, NULL, (void *) &com_desc },
    { MTAB_XTD|MTAB_VUN|MTAB_NC, 0, "LOG", "LOG",
      &tmxr_set_log, &tmxr_show_log, (void*) &com_desc },
    { MTAB_XTD|MTAB_VUN|MTAB_NC, 0, NULL, "NOLOG",
      &tmxr_set_nolog, NULL, (void *) &com_desc },
    { MTAB_XTD | MTAB_VUN | MTAB_NMO, 0, "INPQ", NULL,
      NULL, &com_show_oneq, 0 },
    { MTAB_XTD | MTAB_VUN | MTAB_NMO, 1, "OUTQ", NULL,
      NULL, &com_show_oneq, 0 },
    { 0 }
    };

REG coml_reg[] = {
    { URDATA (TIME, coml_unit[0].wait, 10, 24, 0,
              COM_TLINES, REG_NZ + PV_LEFT) },
    { NULL }
    };

DEVICE coml_dev = {
    "COML", coml_unit, coml_reg, coml_mod,
    COM_TLINES, 10, 31, 1, 16, 8,
    NULL, NULL, &com_reset,
    NULL, NULL, NULL,
    NULL, DEV_DIS
    };

/* COM: channel select */

t_stat com_chsel (uint32 ch, uint32 sel, uint32 unit)
{
com_ch = ch;                                            /* save channel */
if (sim_is_active (&com_unit[COM_CHU]) ||               /* not idle? */
    sim_is_active (&com_unit[COM_SNS])) {
    com_end (ch, CHINT_SEQC, 0);                        /* end, seq check */
    return SCPE_OK;
    }

switch (sel) {                                          /* case on select */

    case CHSL_RDS:                                      /* read */
    case CHSL_WRS:                                      /* write */
        com_sns = 0;                                    /* clear status */
        sim_activate (&com_unit[COM_CHU], com_unit[COM_CHU].wait);
        break;

    case CHSL_SNS:                                      /* sense */
        sim_activate (&com_unit[COM_SNS], com_unit[COM_SNS].wait);
        break;

    case CHSL_CTL:                                      /* control */
    default:                                            /* other */
        return STOP_ILLIOP;
        }

com_stop = 0;                                           /* clear stop */
com_sta = sel;                                          /* set initial state */
return SCPE_OK;
}

/* Channel write, from 7909 channel program */

t_stat com_chwr (uint32 ch, t_uint64 val, uint32 stopf)
{
if (stopf)
    com_stop = 1;
else {
    com_chob = val;                                     /* store data */
    com_chob_v = 1;                                     /* set valid */
    }
return SCPE_OK;
}

/* Unit service - SNS */

t_stat coms_svc (UNIT *uptr)
{
t_uint64 dat;

switch (com_sta) {                                      /* case on state */

    case CHSL_SNS:                                      /* prepare data */
        com_sns &= ~COMS_DYN;                           /* clear dynamic flags */
        if (com_free[HEAD])                             /* free space? */
            com_set_sns (COMS_INBF);
        if (com_test_inp ())                            /* pending input? */
            com_set_sns (COMS_DATR);
        com_buf[0] = (com_sns >> 24) & DMASK;           /* buffer is 2 words */
        com_buf[1] = (com_sns << 12) & DMASK;
        com_bptr = 0;
        com_blim = 2;
        com_sta = CHSL_SNS|CHSL_2ND;                    /* 2nd state */
        break;

    case CHSL_SNS|CHSL_2ND:                             /* second state */
        if (com_bptr >= com_blim) {                     /* end of buffer? */
            ch9_set_end (com_ch, 0);                    /* set end */
            ch_req |= REQ_CH (com_ch);                  /* request channel */
            com_sta = CHSL_SNS|CHSL_3RD;                /* 3rd state */
            sim_activate (uptr, 10 * uptr->wait);       /* longer wait */
            return SCPE_OK;
            }
        dat = com_buf[com_bptr++];                      /* get word */
        if (!com_stop)                                  /* send wd to chan */
            ch9_req_rd (com_ch, dat);
        break;

    case CHSL_SNS|CHSL_3RD:                             /* 3rd state */
        if (com_qdone (com_ch))                         /* done? exit */
            return SCPE_OK;
        com_sta = CHSL_SNS;                             /* repeat sequence */
        break;
        }

sim_activate (uptr, uptr->wait);                        /* sched next */
return SCPE_OK;
}

/* Unit service - channel program */

t_stat comc_svc (UNIT *uptr)
{
uint32 i, j, k, ccnt, ln, uln;
uint16 chr, ent;
t_uint64 dat;

switch (com_sta) {                                      /* case on state */

    case CHSL_RDS:                                      /* read start */
        for (i = 0; i < COM_BUFSIZ; i++)                /* clear chan buf */
            com_buf[i] = 0;
        com_buf[0] = com_msgn;                          /* 1st char is msg num */
        com_msgn = (com_msgn + 1) & 03777;              /* incr msg num */
        for (i = 1, j = 0, ln = 0;                      /* check all lines */
            (ln < COM_TLINES) && (i < COMI_12BMAX);     /* until buffer full */
            ln++) {
            chr = (uint16) com_gen_ccmp (ln);           /* completion msg? */
            if ((chr == 0) && coml_unit[ln].INPP) {     /* no, line input? */
                ent = com_gethd_free (com_inpq[ln]);    /* get first char */
                if (ent != 0)                           /* any input? */
                    chr = com_pkt[ent][DATA];           /* return char */
                else coml_unit[ln].INPP = 0;            /* this line is idle */
                }                                       /* end if input pending */
            if (chr != 0) {                             /* got something? */
                if ((i++ % 3) == 0)                     /* next word? */
                    j++;
                com_buf[j] = (com_buf[j] << 12) |       /* pack line number */
                    ((t_uint64) ((ln + COM_LBASE) | COMI_VALIDL));
                if ((i++ % 3) == 0)                     /* next word? */
                    j++;
                com_buf[j] = (com_buf[j] << 12) |       /* pack data */
                    ((t_uint64) (chr & 07777));
                }                                       /* end if char */
            }                                           /* end for buffer */
        for (k = i % 3; k < 3; k++) {                   /* fill with EOM */
            if (k == 0)                                 /* next word? */
                j++;
            com_buf[j] = (com_buf[j] << 12) | COMI_EOM;
            }
        com_bptr = 0;                                   /* init buf ptr */
        com_blim = j + 1;                               /* save buf size */
        com_sta = CHSL_RDS|CHSL_2ND;                    /* next state */
        break;

    case CHSL_RDS|CHSL_2ND:                             /* read xmit word */
        if (com_bptr >= com_blim)                       /* transfer done? */
            com_end (com_ch, 0, CHSL_RDS|CHSL_3RD);     /* end, next state */
        else {                                          /* more to do */
            dat = com_buf[com_bptr++];                  /* get word */
            if (!com_stop)                              /* give to channel */
                ch9_req_rd (com_ch, dat);
            }
        break;

    case CHSL_RDS|CHSL_3RD:                             /* read end */
        if (com_qdone (com_ch)) {                       /* done? */
            if (com_test_inp ())                        /* more data waiting? */
                ch9_set_atn (com_ch);
            return SCPE_OK;                             /* exit */
            }
        com_sta = CHSL_RDS;                             /* repeat sequence */
        break;

    case CHSL_WRS:                                      /* write start */
        for (i = 0; i < COM_BUFSIZ; i++)                /* clear chan buf */
            com_buf[i] = 0;
        com_bptr = 0;                                   /* init buf ptr */
        com_sta = CHSL_WRS|CHSL_2ND;                    /* next state */
        ch_req |= REQ_CH (com_ch);                      /* request channel */
        com_chob = 0;                                   /* clr, inval buf */
        com_chob_v = 0;
        break;

    case CHSL_WRS|CHSL_2ND:                             /* write first word */
        dat = com_getob (com_ch);                       /* get word? */
        if (dat == INT64_C(0777777777777)) {            /* turn on? */
            com_enab = 1;                               /* enable 7750 */
            com_msgn = 0;                               /* init message # */
            com_end (com_ch, 0, CHSL_WRS|CHSL_4TH);     /* end, last state */
            }
        else if (dat & COMO_LINCTL) {                   /* control message? */
            ln = COMO_GETLN (dat);                      /* line number */
            if (ln >= (COM_TLINES + COM_LBASE))         /* invalid line? */
                return STOP_INVLIN;
            chr = (uint16) ((dat >> 12) & 07777);       /* control message */
            if (chr != COMO_CTLRST)                     /* char must be 0 */
                return STOP_INVMSG;
            if (ln >= COM_LBASE)
                com_reset_ln (ln - COM_LBASE);
            com_end (com_ch, 0, CHSL_WRS|CHSL_4TH);     /* end, last state */
            }
        else {                                          /* data message */
            ccnt = (((uint32) dat >> 12) & 07777) + 1;  /* char count plus EOM */
            if (dat & COMO_LIN12B)                      /* 12b? double */
                ccnt = ccnt << 1;
            com_blim = (ccnt + 6 + 5) / 6;              /* buffer limit */
            if ((com_blim == 1) || (com_blim >= COMO_BMAX))
                return STOP_INVMSG;
            com_buf[com_bptr++] = dat;                  /* store word */
            com_sta = CHSL_WRS|CHSL_3RD;                /* next state */
            ch_req |= REQ_CH (com_ch);                  /* request channel */
            }
        break;

    case CHSL_WRS|CHSL_3RD:                             /* other words */
        dat = com_getob (com_ch);                       /* get word */
        com_buf[com_bptr++] = dat;                      /* store word */
        if (com_bptr >= com_blim) {                     /* transfer done? */
            ln = COMO_GETLN (com_buf[0]);               /* line number */
            if (ln >= (COM_TLINES + COM_LBASE))         /* invalid line? */
                return STOP_INVLIN;
            if ((com_buf[0] & COMO_LIN12B) &&           /* 12b message? */
                (ln >= COM_LBASE)) {
                uln = ln - COM_LBASE;                   /* unit number */
                for (i = 2, j = 0; i < COMO_12BMAX; i++) { /* unpack 12b char */
                    if ((i % 3) == 0)
                        j++;
                    chr = (uint16) (com_buf[j] >> ((2 - (i % 3)) * 12)) & 07777;
                    if (chr == COMO_EOM12B)             /* EOM? */
                        break;
                    if (!com_new_puttl (com_outq[uln], chr))
                        return STOP_NOOFREE;            /* append to outq */
                    }
                sim_activate (&coml_unit[uln], coml_unit[uln].wait);
                }
            com_end (com_ch, 0, CHSL_WRS|CHSL_4TH);     /* end, last state */
            }
        else if (!com_stop)                             /* request channel */
            ch_req |= REQ_CH (com_ch);
        break;

    case CHSL_WRS|CHSL_4TH:                             /* buffer done */
        if (com_qdone (com_ch))                         /* done? */
            return SCPE_OK;                             /* exit */
        com_sta = CHSL_WRS;                             /* repeat sequence */
        break;

    default:
        return SCPE_IERR;
        }

sim_activate (uptr, uptr->wait);
return SCPE_OK;
}       

/* Unit service - console receive - always running, even if device is not */

t_stat comti_svc (UNIT *uptr)
{
int32 c, ln = COM_MLINES;
uint16 ent;

sim_activate (uptr, uptr->wait);                        /* continue poll */
c = sim_poll_kbd ();                                    /* get character */
if (c && !(c & (SCPE_BREAK|SCPE_KFLAG)))                /* error? */
    return c;
if (!com_enab || (c & SCPE_BREAK))                      /* !enab, break? done */
    return SCPE_OK;
if (coml_unit[ln].NEEDID)                               /* ID needed? */
    return com_send_id (ln);
if ((c & SCPE_KFLAG) && ((c = c & 0177) != 0)) {        /* char input? */
    if ((c == 0177) || (c == '\b')) {                   /* delete? */
        ent = com_gettl_free (com_inpq[ln]);            /* remove last char */
        if (!coml_unit[ln].NOECHO)
            sim_putchar (ent? '\b': '\a');
        return SCPE_OK;
        }
    if (!com_queue_in (ln, c))                          /* add to inp queue */
        return STOP_NOIFREE;
    if (!coml_unit[ln].NOECHO) {                        /* echo enabled? */
        if (sim_tt_outcvt (c, TT_MODE_7P) >= 0)         /* printable? */
            sim_putchar (c);
        if (c == '\r')                                  /* line end? */
            sim_putchar ('\n');
        }
    }
return SCPE_OK;                                         /* set ATN if input */
}

/* Unit service - receive side

   Poll all active lines for input
   Poll for new connections */

t_stat comi_svc (UNIT *uptr)
{
int32 c, ln, t;
uint16 ent;

if ((uptr->flags & UNIT_ATT) == 0)                      /* attached? */
    return SCPE_OK;
t = sim_rtcn_calb (com_tps, TMR_COM);                   /* calibrate */
sim_activate (uptr, t);                                 /* continue poll */
if (!com_enab)                                          /* not enabled? exit */
    return SCPE_OK;
ln = tmxr_poll_conn (&com_desc);                        /* look for connect */
if (ln >= 0) {                                          /* got one? */
    com_ldsc[ln].rcve = 1;                              /* rcv enabled */ 
    coml_unit[ln].CONN = 1;                             /* flag connected */
    coml_unit[ln].NEEDID = 1;                           /* need ID */
    coml_unit[ln].NOECHO = 0;                           /* echo enabled */
    coml_unit[ln].INPP = 0;                             /* no input pending */
    }
tmxr_poll_rx (&com_desc);                               /* poll for input */
for (ln = 0; ln < COM_MLINES; ln++) {                   /* loop thru mux */
    if (com_ldsc[ln].conn) {                            /* connected? */
        if (coml_unit[ln].NEEDID)
            return com_send_id (ln);
        c = tmxr_getc_ln (&com_ldsc[ln]);               /* get char */
        if (c) {                                        /* any char? */
            c = c & 0177;                               /* mask to 7b */
            if ((c == 0177) || (c == '\b')) {           /* delete? */
                ent = com_gettl_free (com_inpq[ln]);    /* remove last char */
                if (!coml_unit[ln].NOECHO)
                    tmxr_putc_ln (&com_ldsc[ln], ent? '\b': '\a');
                return SCPE_OK;
                }
            if (!com_queue_in (ln, c))                  /* queue char, err? */
                return STOP_NOIFREE;
            if (com_ldsc[ln].xmte) {                    /* output enabled? */
                if (!coml_unit[ln].NOECHO) {            /* echo enabled? */
                    if (sim_tt_outcvt (c, TT_MODE_7P) >= 0)
                        tmxr_putc_ln (&com_ldsc[ln], c);
                    if (c == '\r')                      /* add LF after CR */
                        tmxr_putc_ln (&com_ldsc[ln], '\n');
                    }
                tmxr_poll_tx (&com_desc);               /* poll xmt */
                }                                       /* end if enabled */
            }                                           /* end if char */
        }                                               /* end if conn */
    else if (coml_unit[ln].CONN) {                      /* not conn, was conn? */
        coml_unit[ln].CONN = 0;                         /* clear connected */
        coml_unit[ln].NEEDID = 0;                       /* clear need id */
        com_set_inpp (ln);                              /* input pending, ATN1 */
        if (!com_new_puttl (com_inpq[ln], COMI_HANGUP)) /* hangup message */
            return STOP_NOIFREE;
        }
    }                                                   /* end for */
return SCPE_OK;
}

/* Unit service - console transmit */

t_stat comto_svc (UNIT *uptr)
{
uint32 ln = COM_MLINES;
uint32 c, c1;

c = com_queue_out (ln, &c1);                            /* get character, cvt */
if (c)                                                  /* printable? output */
    sim_putchar (c);
if (c1)                                                 /* second char? output */
    sim_putchar (c1);
if (com_outq[ln][HEAD] == 0)                            /* line idle? */
    ch9_set_atn (com_ch);                               /* set ATN1 */
else sim_activate (uptr, uptr->wait);                   /* next char */
return SCPE_OK;
}

/* Unit service - transmit side */

t_stat como_svc (UNIT *uptr)
{
uint32 c, c1;
int32 ln = uptr - coml_unit;                            /* line # */

if (com_ldsc[ln].conn) {                                /* connected? */
    if (com_ldsc[ln].xmte) {                            /* output enabled? */
        c = com_queue_out (ln, &c1);                    /* get character, cvt */
        if (c)                                          /* printable? output */
            tmxr_putc_ln (&com_ldsc[ln], c);
        if (c1)                                         /* print second */
            tmxr_putc_ln (&com_ldsc[ln], c1);
        }                                               /* end if */
    tmxr_poll_tx (&com_desc);                           /* poll xmt */
    if (com_outq[ln][HEAD] == 0)                         /* line idle? */
        ch9_set_atn (com_ch);                           /* set ATN1 */
    else sim_activate (uptr, uptr->wait);               /* next char */
    }                                                   /* end if conn */
return SCPE_OK;
}

/* Send ID sequence on input */

t_stat com_send_id (uint32 ln)
{
com_new_puttl (com_inpq[ln], COMI_DIALUP);              /* input message: */
if (coml_unit[ln].flags & UNIT_K35)                     /* dialup, ID, endID */
    com_new_puttl (com_inpq[ln], COMI_K35);
else com_new_puttl (com_inpq[ln], COMI_K37);
com_new_puttl (com_inpq[ln], 0);
com_new_puttl (com_inpq[ln], 0);
com_new_puttl (com_inpq[ln], 0);
com_new_puttl (com_inpq[ln], 0);
com_new_puttl (com_inpq[ln], (uint16) (ln + COM_LBASE));
if (!com_new_puttl (com_inpq[ln], COMI_ENDID))          /* make sure there */
    return STOP_NOIFREE;                                /* was room for msg */
coml_unit[ln].NEEDID = 0;
com_set_inpp (ln);                                      /* input pending, ATN1 */
return SCPE_OK;
}

/* Translate and queue input character */

t_bool com_queue_in (uint32 ln, uint32 c)
{
uint16 out;

if (c == com_intr) {
    out = COMI_INTR;
    com_set_inpp (ln);
    }
else if (c == com_quit) {
    out = COMI_QUIT;
    com_set_inpp (ln);
    }
else {
    if (c == '\r')
        com_set_inpp (ln);
    if (coml_unit[ln].flags & UNIT_K35) {               /* KSR-35? */
        if (islower (c))                                /* convert LC to UC */
            c = toupper (c);
        }
    else c |= (com_epar[c]? COMI_PARITY: 0);            /* add even parity */
    out = (~c) & 0377;                                  /* 1's complement */
    }
return com_new_puttl (com_inpq[ln], out);               /* input message */
}

/* Retrieve and translate output character */

uint32 com_queue_out (uint32 ln, uint32 *c1)
{
uint32 c, ent, raw;

*c1 = 0;                                                /* assume non-printing */
if ((ent = com_gethd_free (com_outq[ln])) == 0)         /* get character */
    return 0;                                           /* nothing, exit */
raw = com_pkt[ent][DATA];                               /* get 12b character */
com_not_ret[ln]++;
if (raw == COMO_BITRPT) {                               /* insert delay? */
    if (com_gethd_free (com_outq[ln]))                  /* skip next char */
         com_not_ret[ln]++;                             /* and count it */
    return 0;
    }
c = (~raw >> 1) & 0177;                                 /* remove start, parity */
if ((c >= 040) && (c != 0177)) {                        /* printable? */
    if ((coml_unit[ln].flags & UNIT_K35) && islower (c))/* KSR-35 LC? */
        c = toupper (c);                                /* cvt to UC */
    return c;
    }
switch (c) {

    case '\t': case '\f': case '\b': case '\a':         /* valid ctrls */
        return c;

    case '\r':                                          /* carriage return? */
        *c1 = '\n';                                     /* lf after cr */
        return c;

    case '\n':                                          /* line feed? */
        *c1 = '\n';                                     /* lf after cr */
        return '\r';

    case 022:                                           /* DC2 */
        coml_unit[ln].NOECHO = 1;
        return 0; 

    case 024:                                           /* DC4 */
        coml_unit[ln].NOECHO = 0;
        return 0;
        }

return 0;                                               /* ignore others */
}

/* Generate completion message, if needed */

uint32 com_gen_ccmp (uint32 ln)
{
uint32 t;

if ((t = com_not_ret[ln]) != 0) {                       /* chars not returned? */
    if (t > COMI_CMAX)                                  /* limit to max */
        t = COMI_CMAX;
    com_not_ret[ln] -= t;                               /* keep count */
    return COMI_COMP (t);                               /* gen completion msg */
    }
return 0;
}

/* Read and validate output buffer */

t_uint64 com_getob (uint32 ch)
{
if (com_chob_v)                                         /* valid? clear */
    com_chob_v = 0;
else if (!com_stop) {                                   /* not stopped? */
    ch9_set_ioc (com_ch);                               /* IO check */
    com_set_sns (COMS_ITMO);                            /* set sense bit */
    }
return com_chob;
}

/* Test whether input pending */

t_bool com_test_inp (void)
{
uint32 i;

for (i = 0; i < COM_TLINES; i++) {
    if ((com_not_ret[i] != 0) || coml_unit[i].INPP)
        return TRUE;
    }
return FALSE;
}

/* Set input pending and attention */

void com_set_inpp (uint32 ln)
{
coml_unit[ln].INPP = 1;
ch9_set_atn (com_ch);
return;
}

/* Test for done */

t_bool com_qdone (uint32 ch)
{
if (com_stop || !ch9_qconn (ch)) {                      /* stop or err disc? */
    com_sta = 0;                                        /* ctrl is idle */
    return TRUE;
    }
return FALSE;
}

/* Channel end */

void com_end (uint32 ch, uint32 fl, uint32 st)
{
ch9_set_end (ch, fl);                                   /* set end */
ch_req |= REQ_CH (ch);
com_sta = st;                                           /* next state */
return;
}

/* List routines - remove from head and free */

uint16 com_gethd_free (uint16 *lh)
{
uint16 ent;

if ((ent = com_gethd (lh)) != 0)
    com_puttl (com_free, ent);
return ent;
}

/* Remove from tail and free */

uint16 com_gettl_free (uint16 *lh)
{
uint16 ent;

if ((ent = com_gethd (lh)) != 0)
    com_puttl (com_free, ent);
return ent;
}

/* Get free entry and insert at tail */

t_bool com_new_puttl (uint16 *lh, uint16 val)
{
uint16 ent;

if ((ent = com_gethd (com_free)) != 0) {
    com_pkt[ent][DATA] = val;
    com_puttl (lh, ent);
    return TRUE;
    }
return FALSE;
}

/* Remove from head */

uint16 com_gethd (uint16 *lh)
{
uint16 ent;

if ((ent = lh[HEAD]) != 0) {
    lh[HEAD] = com_pkt[ent][NEXT];
    if (lh[HEAD] == 0)
        lh[TAIL] = 0;
    }
else lh[TAIL] = 0;
return ent;
}

/* Remove from tail */

uint16 com_gettl (uint16 *lh)
{
uint16 ent, next;
uint32 i;

ent = lh[TAIL];
if (lh[HEAD] == lh[TAIL]) {
    lh[HEAD] = lh[TAIL] = 0;
    return ent;
    }
next = lh[HEAD];
for (i = 0; i < COM_PKTSIZ; i++) {
    if (com_pkt[next][NEXT] == ent) {
        com_pkt[next][NEXT] = 0;
        lh[TAIL] = next;
        return ent;
        }
    }
return 0;
}

/* Insert at tail */

void com_puttl (uint16 *lh, uint16 ent)
{
if (lh[TAIL] == 0)
    lh[HEAD] = ent;
else com_pkt[lh[TAIL]][NEXT] = ent;
com_pkt[ent][NEXT] = 0;
lh[TAIL] = ent;
return;
}

/* Set flag in sense */

void com_set_sns (t_uint64 stat)
{
com_sns |= stat;
com_sns &= ~(COMS_PCHK|COMS_DCHK|COMS_EXCC);
if (com_sns & COMS_PALL)
    com_sns |= COMS_PCHK;
if (com_sns & COMS_DALL)
    com_sns |= COMS_DCHK;
if (com_sns & COMS_EALL)
    com_sns |= COMS_EXCC;
return;
}

/* Reset routine */

t_stat com_reset (DEVICE *dptr)
{
uint32 i;

if (dptr->flags & DEV_DIS) {                            /* disabled? */
    com_dev.flags = com_dev.flags | DEV_DIS;            /* disable lines */
    coml_dev.flags = coml_dev.flags | DEV_DIS;
    }
else {
    com_dev.flags = com_dev.flags & ~DEV_DIS;           /* enable lines */
    coml_dev.flags = coml_dev.flags & ~DEV_DIS;
    }
sim_activate (&com_unit[COM_CIU], com_unit[COM_CIU].wait); /* console */
sim_cancel (&com_unit[COM_PLU]);
sim_cancel (&com_unit[COM_CHU]);
if (com_unit[COM_PLU].flags & UNIT_ATT) {               /* master att? */
    int32 t = sim_rtcn_init (com_unit[COM_PLU].wait, TMR_COM);
    sim_activate (&com_unit[COM_PLU], t);
    }
com_enab = 0;
com_sns = 0;
com_msgn = 0;
com_sta = 0;
com_chob = 0;
com_chob_v = 0;
com_stop = 0;
com_bptr = 0;
com_blim = 0;
for (i = 0; i < COM_BUFSIZ; i++)
    com_buf[i] = 0;
for (i = 0; i < COM_TLINES; i++) {                      /* init lines */
    com_inpq[i][HEAD] = 0;
    com_inpq[i][TAIL] = 0;
    com_outq[i][HEAD] = 0;
    com_outq[i][TAIL] = 0;
    com_reset_ln (i);
    }
com_pkt[0][NEXT] = 0;                                   /* init free list */
for (i = 1; i < COM_PKTSIZ; i++) {
    com_pkt[i][NEXT] = i + 1;
    com_pkt[i][DATA] = 0;
    }
com_pkt[COM_PKTSIZ - 1][NEXT] = 0;                       /* end of free list */
com_free[HEAD] = 1;
com_free[TAIL] = COM_PKTSIZ - 1;
coml_unit[COM_MLINES].CONN = 1;                         /* console always conn */
coml_unit[COM_MLINES].NEEDID = 1;
return SCPE_OK;
}

/* Attach master unit */

t_stat com_attach (UNIT *uptr, CONST char *cptr)
{
t_stat r;

r = tmxr_attach (&com_desc, uptr, cptr);                /* attach */
if (r != SCPE_OK)                                       /* error */
    return r;
sim_rtcn_init (uptr->wait, TMR_COM);
sim_activate (uptr, 100);                               /* quick poll */
return SCPE_OK;
}

/* Detach master unit */

t_stat com_detach (UNIT *uptr)
{
uint32 i;
t_stat r;

r = tmxr_detach (&com_desc, uptr);                      /* detach */
for (i = 0; i < COM_MLINES; i++)                        /* disable rcv */
    com_ldsc[i].rcve = 0;
sim_cancel (uptr);                                      /* stop poll */
return r;
}

/* Reset an individual line */

void com_reset_ln (uint32 ln)
{
while (com_gethd_free (com_inpq[ln])) ;
while (com_gethd_free (com_outq[ln])) ;
com_not_ret[ln] = 0;
coml_unit[ln].NEEDID = 0;
coml_unit[ln].NOECHO = 0;
coml_unit[ln].INPP = 0;
sim_cancel (&coml_unit[ln]);
if ((ln < COM_MLINES) && (com_ldsc[ln].conn == 0))
    coml_unit[ln].CONN = 0;
return;
}

/* Special show commands */

uint32 com_show_qsumm (FILE *st, uint16 *lh, const char *name)
{
uint32 i, next;

next = lh[HEAD];
for (i = 0; i < COM_PKTSIZ; i++) {
    if (next == 0) {
        if (i == 0)
            fprintf (st, "%s is empty\n", name);
        else if (i == 1)
            fprintf (st, "%s has 1 entry\n", name);
        else fprintf (st, "%s has %d entries\n", name, i);
        return i;
        }
    next = com_pkt[next][NEXT];
    }
fprintf (st, "%s is corrupt\n", name);
return 0;
}

void com_show_char (FILE *st, uint32 ch)
{
uint32 c;

fprintf (st, "%03o", ch);
c = (~ch) & 0177;
if (((ch & 07400) == 0) && (c >= 040) && (c != 0177))
    fprintf (st, "[%c]", c);
return;
}

t_stat com_show_freeq (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
com_show_qsumm (st, com_free, "Free queue");
return SCPE_OK;
}

t_stat com_show_oneq (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
uint32 entc, ln, i, next;
uint16 *lh;
char name[20];

ln = uptr - coml_dev.units;
sprintf (name, val? "Output queue %d": "Input queue %d", ln);
lh = val? com_outq[ln]: com_inpq[ln];
if ((entc = com_show_qsumm (st, lh, name))) {
    for (i = 0, next = lh[HEAD]; next != 0;
         i++, next = com_pkt[next][NEXT]) {
        if ((i % 8) == 0)
            fprintf (st, "%d:\t", i);
        com_show_char (st, com_pkt[next][DATA] >> (val? 1: 0));
        fputc ((((i % 8) == 7)? '\n': '\t'), st);
        }
    if (i % 8)
        fputc ('\n', st);
    }
return SCPE_OK;
}

t_stat com_show_allq (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
uint32 i;

for (i = 0; i < COM_TLINES; i++)
    com_show_oneq (st, coml_dev.units + i, val, desc);
return SCPE_OK;
}

t_stat com_show_ctrl (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
if (!com_enab)
    fprintf (st, "Controller is not initialized\n");
if (val & COMR_FQ)
    com_show_freeq (st, uptr, 0, desc);
if (val & COMR_IQ)
    com_show_allq (st, uptr, 0, desc);
if (val & COMR_OQ)
    com_show_allq (st, uptr, 1, desc);
return SCPE_OK;
}
