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

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

   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.  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.

   Sense word based on 7074 Principles of Operation.

   1   A   Reserved.
   3   4   Program Check          Summary byte
   4   2   Exceptional Condtion   Summary byte
   5   1   Data Check             Summary byte
   7   A   Reserved
   9   4   Message Length Check   Program Check
   10  2   Channel Hold           Program Check
   11  1   Channel Queue Full     Program Check
   13  A   Reserved
   15  4   Reserved
   16  2   Reserved
   17  1   Interface Timeout      Data Check
   19  A   Reserved
   21  4   Data Message Ready     Exceptional Condition
   22  2   Input space available  Exceptional Condition
   23  1   Service Message Ready  Exceptional Condition

*/

#include "i7000_defs.h"
#include "sim_timer.h"
#include "sim_sock.h"
#include "sim_tmxr.h"
#include <ctype.h>

#ifdef NUM_DEVS_COM
#define COM_MLINES      32	/* 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     (UNIT_V_UF + 0)	/* 2741 - ni */
#define UNIT_V_K35      (UNIT_V_UF + 1)	/* KSR-35 */
#define UNIT_2741       (1 << UNIT_V_2741)
#define UNIT_K35        (1 << UNIT_V_K35)

#define TMR_COM		2

#define CONN            u3	/* line is connected */
#define NEEDID          u4	/* need to send ID */

#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	/* console output */

/* Communications input */

#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_PARITY     00200	/* parity bit */
#define COMI_BMAX       50	/* buffer max, words */
#define COMI_12BMAX     ((3 * COMI_BMAX) - 1)	/* last 12b char */

/* Communications output - characters */

#define COMO_LIN12B     02000	/* line is 12b */
#define COMO_LINCTL     01000	/* control msg */
#define COMO_GETLN(x)   ((x) & 0777)
#define COMO_CTLRST     07777	/* control reset */
#define COMO_BITRPT     03777	/* bit repeat */
#define COMO_EOM12B     07777	/* end of medium */
#define COMO_EOM6B      077	/* end of medium */
#define COMO_BMAX       94	/* buffer max, words */
#define COMO_12BMAX     ((3 * COMO_BMAX) - 1)

/* Report variables */

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

/* Sense word flags */
#define EXPT_SRVRDY     0x1001	/* Service message available */
#define EXPT_INAVAIL    0x1002	/* Input available */
#define EXPT_DATRDY     0x1004	/* Data ready. */
#define DATA_TIMEOUT    0x2010	/* Timeout */
#define PROG_FULL       0x4100	/* No more space to send message */
#define PROG_HOLD       0x4200	/* Channel hold */
#define PROG_MSGLEN     0x4400	/* Invalid message length */

/* List heads and entries */

typedef struct
{
    uint16              head;
    uint16              tail;
}
LISTHD;

typedef struct
{
    uint16              next;
    uint16              data;
}
LISTENT;

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

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

   The input queue has two entries for each character; the first is the
   line number, the second the character.  The output queues have only
   one entry for each character.

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

uint32              com_posti = 0;	/* Posted a IRQ */
uint32              com_ocnt = 0;	/* Number of characters to output */
uint32              com_oln = 0;	/* Output line number */
uint32              com_o12b = 0;	/* Outputing 12 bit */
uint32              com_enab = 0;	/* 7750 enabled */
uint32              com_msgn = 0;	/* next input msg num */
uint32              com_sta = 0;	/* 7750 state */
uint32              com_quit = 0;	/* quit code */
uint32              com_intr = 0;	/* interrupt code */
uint32              com_tps = 50;	/* polls/second */
uint32              com_not_ret[COM_TLINES] = { 0 };	/* chars not returned */
LISTHD              com_free;	/* free list */
LISTHD              com_inpq;	/* input queue */
LISTHD              com_outq[COM_TLINES];	/* output queue */
LISTENT             com_pkt[COM_PKTSIZ];	/* character packets */
TMLN                com_ldsc[COM_MLINES];	/* line descriptors */
TMXR                com_desc = { COM_MLINES, 0, 0, com_ldsc };	/* mux descriptor */
uint16              com_sense = 0;	/* Sense word */
uint16		    com_data;
uint8		    com_dflg = 0;

/* 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
};

uint32              com_cmd(UNIT * uptr, uint16 cmd, uint16 dev);
t_stat              com_svc(UNIT * uptr);
t_stat              comi_svc(UNIT * uptr);
t_stat              como_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, char *cptr);
t_stat              com_detach(UNIT * uptr);
t_stat              com_summ(FILE * st, UNIT * uptr, int32 val,
			     void *desc);
t_stat              com_show(FILE * st, UNIT * uptr, int32 val,
			     void *desc);
t_stat              com_show_ctrl(FILE * st, UNIT * uptr, int32 val,
				  void *desc);
t_stat              com_show_freeq(FILE * st, UNIT * uptr, int32 val,
				   void *desc);
t_stat              com_show_inq(FILE * st, UNIT * uptr, int32 val,
				 void *desc);
t_stat              com_show_aoutq(FILE * st, UNIT * uptr, int32 val,
				   void *desc);
t_stat              com_show_outq(FILE * st, UNIT * uptr, int32 val,
				  void *desc);
void                com_reset_ln(uint32 i);
uint16              com_gethd_free(LISTHD * lh);
uint16              com_gethd(LISTHD * lh);
t_bool              com_new_puttl(LISTHD * lh, uint16 val);
void                com_puttl(LISTHD * lh, uint16 ent);
t_bool              com_inp_msg(uint32 ln, uint16 msg);
void                com_skip_outc(uint32 ln);
t_stat              com_send_id(uint32 ln);
t_stat              com_send_ccmp(uint32 ln);
t_stat              com_queue_in(uint32 ln, uint32 ch);
uint32              com_queue_out(uint32 ln, uint32 * c1);

/* COM data structures

   com_dev      COM device descriptor
   com_unit     COM unit descriptor
   com_reg      COM register list
   com_mod      COM modifiers list
*/
#ifdef I7010
#define COM_CHAN	4
#else
#define COM_CHAN	5
#endif

UNIT                com_unit[] = {
    {UDATA(&comi_svc, UNIT_S_CHAN(COM_CHAN) | UNIT_ATTABLE, 0), COM_INIT_POLL},
    {UDATA(&comti_svc, UNIT_S_CHAN(COM_CHAN) | UNIT_DIS, 0), KBD_POLL_WAIT},
    {UDATA(&com_svc, UNIT_S_CHAN(COM_CHAN) | UNIT_DIS, 0), COMC_WAIT},
};

REG                 com_reg[] = {
    {FLDATA(ENABLE, com_enab, 0)},
    {ORDATA(STATE, com_sta, 6)},
    {ORDATA(MSGNUM, com_msgn, 12)},
    {BRDATA(FREEQ, &com_free, 10, 16, 2)},
    {BRDATA(INPQ, &com_inpq, 10, 16, 2)},
    {BRDATA(OUTQ, com_outq, 10, 16, 2 * COM_TLINES)},
    {BRDATA(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},
    {NULL}
};

MTAB                com_mod[] = {
    {MTAB_XTD | MTAB_VUN | MTAB_VAL, 0, "CHAN", "CHAN",
     &set_chan, &get_chan, NULL},
#ifndef I7010
    {MTAB_XTD | MTAB_VUN | MTAB_VAL, 0, "SELECT", "SELECT",
     &chan9_set_select, &chan9_get_select, NULL},
#endif
    {UNIT_ATT, UNIT_ATT, "connections", NULL, NULL, &com_summ},
    {MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "CONNECTIONS", NULL,
     NULL, &com_show, NULL},
    {MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "STATISTICS", NULL,
     NULL, &com_show, NULL},
    {MTAB_XTD | MTAB_VDV | MTAB_NMO, COMR_FQ, "FREEQ", NULL,
     NULL, &com_show_ctrl, 0},
    {MTAB_XTD | MTAB_VDV | MTAB_NMO, COMR_IQ, "INQ", 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, -1, "ALL", NULL,
     NULL, &com_show_ctrl, 0},
    {MTAB_XTD | MTAB_VUN | MTAB_NMO, 0, "OUTQ", NULL,
     NULL, &com_show_outq, 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_DIS | DEV_DISABLE| DEV_DEBUG|DEV_NET, 0, dev_debug
};

/* COMLL data structures

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

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(&como_svc, 0, 0), COML_WAIT},
    {UDATA(&comto_svc, 0, 0), COML_WAIT},
};

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, &com_desc},
    {MTAB_XTD | MTAB_VUN | MTAB_NC, 0, "LOG", "LOG",
     &tmxr_set_log, &tmxr_show_log, &com_desc},
    {MTAB_XTD | MTAB_VUN | MTAB_NC, 0, NULL, "NOLOG",
     &tmxr_set_nolog, NULL, &com_desc},
    {0}
};

REG                 coml_reg[] = {
    {URDATA(TIME, coml_unit[0].wait, 16, 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_DISABLE
};

/* COM: channel select */
uint32 com_cmd(UNIT * uptr, uint16 cmd, uint16 dev)
{
    /* Activate the com device */
    sim_activate(&com_unit[COM_CHU], 10);
    if (!sim_is_active(&com_unit[COM_CIU]))	 /* console */
	sim_activate(&com_unit[COM_CIU], com_unit[COM_CIU].wait);
    if (!sim_is_active(&com_unit[COM_PLU])) {
	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_sta = 1;
    com_dflg = 0;
    return SCPE_OK;
}

/* Unit service - channel program */
t_stat com_svc(UNIT * uptr)
{
    int                 chan = UNIT_G_CHAN(uptr->flags);
    int                 sel = (uptr->flags & UNIT_SELECT) ? 1 : 0;
    uint8 	        ch;

    if (sel != chan_test(chan, CTL_SEL))
        return SCPE_OK;

    /* Handle disconnect */
    if (com_sta != 0 && chan_stat(chan, DEV_DISCO)) {
	chan_clear(chan, DEV_WEOR|DEV_SEL);
	if (com_ocnt != 0) {
	    chan9_set_error(chan, SNS_UEND);
	    com_sense |= PROG_MSGLEN;
	}
	com_ocnt = 0;
	com_sta = 0;
	return SCPE_OK;
    }

    if (chan_test(chan, CTL_SNS)) {
	int	eor = (com_sta == 4)?DEV_REOR:0;

	ch = (com_sense >> ((4 - com_sta) * 4)) & 0xf;
	if (ch & 010) 	/* Move A bit over one */
	   ch ^= 030;
	switch(chan_write_char(chan, &ch, eor)) {
	case TIME_ERROR:
	case END_RECORD:
	      com_sta = -1;
	      com_sense = 0;
	      break;
	case DATA_OK:
	      com_sta++;
	}
	sim_activate(uptr, 50);
	return SCPE_OK;
    }

    /* Start a command, only do read/write */
    if (chan_test(chan, CTL_CNTL)) {
	chan_clear(chan, DEV_FULL);
	chan_set(chan, DEV_REOR|DEV_SEL);
	sim_activate(uptr, 50);
	return SCPE_OK;
    }

    /* Send down next buffer word */
    if (chan_test(chan, CTL_READ)) {
	uint32             ent;

	/* Send low order character if one */
	if (com_dflg) {
	    ch = com_data;
            switch (chan_write_char(chan, &ch, (com_sta == 3)?DEV_REOR:0)) {
	    case DATA_OK:
	    case END_RECORD:
		com_dflg = 0;
		break;
	    case TIME_ERROR:
	        chan9_set_error(chan, SNS_UEND);
                com_sense |= DATA_TIMEOUT;
	    }
	    sim_activate(uptr, 50);
	    return SCPE_OK;
	}
	
        com_ocnt = 0;
        switch (com_sta) {
        case 1:
	    com_data = com_msgn;	/* 1st char is msg num */
	    com_msgn = (com_msgn + 1) & 03777;	/* incr msg num */
	    com_sta++;
	    com_posti = 0;
	    chan9_clear_error(chan, sel);
	    break;
        case 2:
	    ent = com_gethd_free(&com_inpq);	/* get next entry */
	    if (ent == 0) {	/* No more data, end it */
		com_sta++;	/* Advance to end sequence */
		com_data = COMI_EOM;
	    } else
	        com_data = com_pkt[ent].data & 07777;
	    break;
	case 3:
	    chan_set(chan, DEV_REOR|CTL_END);
	    sim_activate(uptr, 50);
	    com_sta++;
	    return SCPE_OK;	 /* q empty, done */
        }
        ch = (com_data >> 6) & 077;
        com_data &= 077;
        switch (chan_write_char(chan, &ch, 0)) {
	case DATA_OK:
	case END_RECORD:
	    com_dflg = 1;
  	    break;
	case TIME_ERROR:
	    chan9_set_error(chan, SNS_UEND);
            com_sense |= DATA_TIMEOUT;
	}
	sim_activate(uptr, 50);
	return SCPE_OK;
    }

    if (chan_test(chan, CTL_WRITE)) {
	uint32              ln;

	/* Read in two characters */
	if (com_dflg == 0) {
	    switch(chan_read_char(chan, &ch, 0)) {
	    case DATA_OK:
	    	com_dflg = 1;
		com_data = (ch & 077) << 6;
	        break;
	    case END_RECORD:
	    case TIME_ERROR:
	        chan9_set_error(chan, SNS_UEND);
	        com_sense |= DATA_TIMEOUT;
	    }
	    sim_activate(uptr, 50);
	    return SCPE_OK;
	} else {
	    switch(chan_read_char(chan, &ch, 0)) {
	    case DATA_OK:
	    	com_dflg = 0;
		com_data |= (ch & 077);
	        break;
	    case END_RECORD:
	    case TIME_ERROR:
	        chan9_set_error(chan, SNS_UEND);
	        com_sense |= DATA_TIMEOUT;
	        sim_activate(uptr, 50);
	        return SCPE_OK;
	    }
	}
	switch (com_sta) {
	case 1:
	    com_oln = com_data;
	    if (com_data == 07777) {	/* turn on? */
		com_enab = 1;	/* enable 7750 */
		com_msgn = 0;	/* init message # */
		com_sta = 4;
		chan_set(chan, DEV_REOR|CTL_END);
	    } else if (com_data & COMO_LINCTL) {	/* control message? */
		ln = COMO_GETLN(com_data);	/* line number */
		if (ln >= (COM_TLINES + COM_LBASE))	/* invalid line? */
		    return STOP_INVLIN;
		if (com_data & COMO_CTLRST)
		    return STOP_INVMSG;	/* char must be 0 */
		if (ln >= COM_LBASE)
		    com_reset_ln(ln - COM_LBASE);
		com_sta = 4;
		chan_set(chan, DEV_REOR|CTL_END);
	    } else  		/* data message */
		com_sta++;
	    break;
	case 2:
	    com_ocnt =  (com_data & 07777) + 1;	/* char count plus EOM */
	    if (com_oln & COMO_LIN12B) {
	        com_ocnt = com_ocnt << 1;	/* 12b? double */
	        com_o12b = 1;
	    } else
	        com_o12b = 0;
	    com_oln = COMO_GETLN(com_oln);	/* line number */
	    com_sta++;	/* next state */
	    break;
	case 3:		/* other words */
	    ln = com_oln - COM_LBASE;	/* effective line */
	    /* unpack chars */
	    if (com_o12b) {
	        com_ocnt -= 2;
	        if (com_data == COMO_EOM12B) {
		    com_sta++;
		    if (com_ocnt != 0) {
			chan9_set_error(chan, SNS_UEND);
		        com_sense |= PROG_MSGLEN;
		    }
		    sim_activate(&coml_unit[ln], coml_unit[ln].wait);
		    chan_set(chan, DEV_REOR|CTL_END);	/* end, last state */
		    break;	/* EOM? */
	        }
	    } else {
	        com_ocnt--;
	        if (((com_data >> 6) & 077) == COMO_EOM6B) {
		    com_sta++;
		    if (com_ocnt != 0) {
			chan9_set_error(chan, SNS_UEND);
		        com_sense |= PROG_MSGLEN;
		    }
		    sim_activate(&coml_unit[ln], coml_unit[ln].wait);
		    chan_set(chan, DEV_REOR|CTL_END);	/* end, last state */
		    break;	/* EOM? */
	        } 
	        if (!com_new_puttl(&com_outq[ln], (com_data >> 6) & 077)) {
		    chan9_set_error(chan, SNS_UEND);
	            com_sense |= PROG_FULL;
	        }
	        com_ocnt--;
		com_data &= 077;
	        if (com_data == COMO_EOM6B) {
		    com_sta++;
		    if (com_ocnt != 0) {
		        chan9_set_error(chan, SNS_UEND);
		        com_sense |= PROG_MSGLEN;
		    }
		    sim_activate(&coml_unit[ln], coml_unit[ln].wait);
		    chan_set(chan, DEV_REOR|CTL_END);	/* end, last state */
		    break;	/* EOM? */
	        }
	    }
	    if (!com_new_puttl(&com_outq[ln], (uint16)com_data)) {
		chan9_set_error(chan, SNS_UEND);
	        com_sense |= PROG_FULL;
	    }
	    break;
	}
	sim_activate(uptr, 50);
    }
    return SCPE_OK;
}

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

t_stat
comti_svc(UNIT * uptr)
{
    int32               c;
    t_stat              r;

    sim_activate(uptr, uptr->wait);	/* continue poll */
    c = sim_poll_kbd();		/* get character */
    if (c && (c < SCPE_KFLAG))
	return c;		/* error? */
    if (((com_unit[COM_PLU].flags & UNIT_ATT) == 0) ||	/* not att, not enab, */
	!com_enab || (c & SCPE_BREAK))
	return SCPE_OK;		/* break? done */
    if (coml_unit[COM_MLINES].NEEDID)
	return com_send_id(COM_MLINES);	/* ID needed? */
    c = c & 0177;
    if (c) {
	r = com_queue_in(COM_MLINES, c);
	if (r != SCPE_OK)
	    return r;
	sim_putchar(c);
	if (c == '\r')
	    sim_putchar('\n');
    }
    return SCPE_OK;
}

/* Unit service - receive side

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

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

    if ((uptr->flags & UNIT_ATT) == 0)
	return SCPE_OK;		/* attached? */
    t = sim_rtcn_calb(com_tps, TMR_COM);	/* calibrate */
    sim_activate(uptr, t);	/* continue poll */
    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 */
    }
    if (!com_enab)
	return SCPE_OK;		/* not enabled? exit */
    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 */
		r = com_queue_in(ln, c);
		if (r != SCPE_OK);
		    return r;	/* queue char, err? */
		if (com_ldsc[ln].xmte) {	/* output enabled? */
		    tmxr_putc_ln(&com_ldsc[ln], c);	/* echo char */
		    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_sense |= EXPT_SRVRDY;
	    if (!com_inp_msg(ln, COMI_HANGUP))	/* hangup message */
		return STOP_NOIFREE;
	}
    }				/* end for */
    return SCPE_OK;
}

/* Unit service - console transmit */

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

    if (com_outq[COM_MLINES].head == 0)	/* no more characters? */
	return com_send_ccmp(COM_MLINES);	/* free any remaining */
    c = com_queue_out(COM_MLINES, &c1);	/* get character, cvt */
    if (c)
	sim_putchar(c);		/* printable? output */
    if (c1)
	sim_putchar(c1);	/* second char? output */
    sim_activate(uptr, uptr->wait);	/* next char */
    if (com_not_ret[COM_MLINES] >= COMI_CMAX)	/* completion needed? */
	return com_send_ccmp(COM_MLINES);	/* generate msg */
    return SCPE_OK;
}

/* Unit service - transmit side */

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

    if (com_outq[ln].head == 0)	/* no more characters? */
	return com_send_ccmp(ln);	/* free any remaining */
    if (com_ldsc[ln].conn) {	/* connected? */
	if (com_ldsc[ln].xmte) {	/* output enabled? */
	    c = com_queue_out(ln, &c1);	/* get character, cvt */
	    if (c)
		tmxr_putc_ln(&com_ldsc[ln], c);	/* printable? output */
	    if (c1)
		tmxr_putc_ln(&com_ldsc[ln], c1);	/* print second */
	}			/* end if */
	tmxr_poll_tx(&com_desc);	/* poll xmt */
	sim_activate(uptr, uptr->wait);	/* next char */
	if (com_not_ret[ln] >= COMI_CMAX)	/* completion needed? */
	    return com_send_ccmp(ln);	/* generate msg */
    }				/* end if conn */
    return SCPE_OK;
}

/* Send ID sequence on input */

t_stat
com_send_id(uint32 ln)
{
    com_inp_msg(ln, COMI_DIALUP);	/* input message: */
    if (coml_unit[ln].flags & UNIT_K35)	/* dialup, ID, endID */
	com_inp_msg(ln, COMI_K35);
    else
	com_inp_msg(ln, COMI_K37);
    com_inp_msg(ln, 0);
    com_inp_msg(ln, 0);
    com_inp_msg(ln, 0);
    com_inp_msg(ln, 0);
    com_inp_msg(ln, (uint16) (ln + COM_LBASE));
    if (!com_inp_msg(ln, COMI_ENDID))	/* make sure there */
	return STOP_NOIFREE;	/* was room for msg */
    coml_unit[ln].NEEDID = 0;
    com_sense |= EXPT_SRVRDY;
    return SCPE_OK;
}
/* Translate and queue input character */

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

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

/* Retrieve and translate output character */

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

    *c1 = 0;			/* assume non-printing */
    ent = com_gethd_free(&com_outq[ln]);	/* get character */
    if (ent == 0)
	return 0;		/* nothing? */
    raw = com_pkt[ent].data;	/* get 12b character */
    com_not_ret[ln]++;
    if (raw == COMO_BITRPT) {	/* insert delay? */
	com_skip_outc(ln);
	return 0;
    }
    c = (~raw >> 1) & 0177;	/* remove start, parity */
    if (c >= 040) {		/* printable? */
	if (c == 0177)
	    return 0;		/* DEL? ignore */
	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? */
	if (coml_unit[ln].flags & UNIT_K35)	/* KSR-35? */
	    *c1 = '\n';		/* lf after cr */
	return c;

    case '\n':			/* line feed? */
	if (!(coml_unit[ln].flags & UNIT_K35)) {	/* KSR-37? */
	    *c1 = '\n';		/* lf after cr */
	    return '\r';
	}
	return c;		/* print lf */

    case 022:			/* DC2 */
	if (!(com_unit[ln].flags & UNIT_K35)) {	/* KSR-37? */
	    com_skip_outc(ln);	/* skip next */
	    return '\n';	/* print lf */
	}
	break;

    case 023:			/* DC3 */
	if (!(com_unit[ln].flags & UNIT_K35))	/* KSR-37? */
	    com_skip_outc(ln);	/* skip next */
	break;
    }

    return 0;			/* ignore others */
}

/* Generate completion message, if needed */

t_stat
com_send_ccmp(uint32 ln)
{
    uint32              t;

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

/* Skip next char in output queue */

void
com_skip_outc(uint32 ln)
{
    if (com_gethd_free(&com_outq[ln]))
	com_not_ret[ln]++;	/* count it */
    return;
}

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

uint16
com_gethd_free(LISTHD * lh)
{
    uint16              ent;

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

/* Get free entry and insert at tail */

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

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

/* Remove from head */

uint16
com_gethd(LISTHD * lh)
{
    uint16              ent;

    ent = lh->head;
    if (ent != 0) {
	lh->head = com_pkt[ent].next;
	if (lh->head == 0)
	    lh->tail = 0;
    } else
	lh->tail = 0;
    return ent;
}

/* Insert at tail */

void
com_puttl(LISTHD * 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;
}

/* Insert line and message into input queue */

t_bool
com_inp_msg(uint32 ln, uint16 msg)
{
    uint16              ent1, ent2;

    ent1 = com_gethd(&com_free);
    if (ent1 != 0) {	/* pkt avail? */
	ent2 = com_gethd(&com_free);
	if (ent2 != 0) {	/* 2nd pkt avail? */
	    com_pkt[ent1].data = 02000 + ln + COM_LBASE;	/* 1st pkt = line# */
	    com_pkt[ent2].data = msg;	/* 2nd pkt = char */
	    com_puttl(&com_inpq, ent1);	/* queue pkts */
	    com_puttl(&com_inpq, ent2);
	    /* Let CPU know we have data for it if we have not already told it */
	    if (com_posti == 0) {
		int               chan = UNIT_G_CHAN(com_unit[0].flags);
		int               sel =
		    (com_unit[0].flags & UNIT_SELECT) ? 1 : 0;

		chan9_set_attn(chan, sel);
		com_posti = 1;
	    }

	    return TRUE;
	}
	com_puttl(&com_free, ent1);	/* free 1st */
    }
    return FALSE;		/* failed */
}

/* 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_msgn = 0;
    com_sta = 0;
    com_sense = 0;
    com_inpq.head = 0;		/* init queues */
    com_inpq.tail = 0;
    for (i = 0; i < COM_TLINES; i++) {
	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, char *cptr)
{
    t_stat              r;

    r = tmxr_attach(&com_desc, uptr, cptr);	/* attach */
    if (r != SCPE_OK)
	return r;		/* error */
    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++)
	com_ldsc[i].rcve = 0;	/* disable rcv */
    sim_cancel(uptr);		/* stop poll */
    return r;
}

/* Show summary processor */

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

    for (i = t = 0; i < COM_MLINES; i++)
	t = t + (com_ldsc[i].conn != 0);
    if (t == 1)
	fprintf(st, "1 connection");
    else
	fprintf(st, "%d connections", t);
    return SCPE_OK;
}

/* SHOW CONN/STAT processor */

t_stat
com_show(FILE * st, UNIT * uptr, int32 val, void *desc)
{
    int32               i, cc;

    for (cc = 0; (cc < COM_MLINES) && com_ldsc[cc].conn; cc++) ;
    if (cc) {
	for (i = 0; i < COM_MLINES; i++) {
	    if (com_ldsc[i].conn) {
		if (val)
		    tmxr_fconns(st, &com_ldsc[i], i);
		else
		    tmxr_fstats(st, &com_ldsc[i], i);
	    }
	}
    } else
	fprintf(st, "all disconnected\n");
    return SCPE_OK;
}

/* Reset an individual line */

void
com_reset_ln(uint32 ln)
{
    while (com_gethd_free(&com_outq[ln])) ;
    com_not_ret[ln] = 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, LISTHD * lh, 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 had %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, void *desc)
{
    com_show_qsumm(st, &com_free, "Free queue");
    return SCPE_OK;
}

t_stat
com_show_inq(FILE * st, UNIT * uptr, int32 val, void *desc)
{
    uint32              entc, ln, i, next;

    entc = com_show_qsumm(st, &com_inpq, "Input queue");
    if (entc != 0) {
	for (i = 0, next = com_inpq.head; next != 0;
	     i++, next = com_pkt[next].next) {
	    if ((i % 4) == 0)
		fprintf(st, "%d:\t", i);
	    ln = com_pkt[next].data;
	    next = com_pkt[ln].next;
	    if (next == 0) {
		fprintf(st, "Line number without data\n");
		return SCPE_OK;
	    }
	    fprintf(st, "%d/", ln);
	    com_show_char(st, com_pkt[next].data);
	    fputc((((i % 4) == 3) ? '\n' : '\t'), st);
	}
	if (i % 4)
	    fputc('\n', st);
    }
    return SCPE_OK;
}

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

    ln = uptr - com_dev.units;
    sprintf(name, "Output queue %d", ln);
    entc = com_show_qsumm(st, &com_outq[ln], name);
    if (entc != 0) {
	for (i = 0, next = com_outq[ln].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 >> 1);
	    fputc((((i % 8) == 7) ? '\n' : '\t'), st);
	}
	if (i % 8)
	    fputc('\n', st);
    }
    return SCPE_OK;
}

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

    for (i = 0; i < COM_TLINES; i++)
	com_show_outq(st, com_dev.units + i, 1, desc);
    return SCPE_OK;
}

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