/* sigma_coc.c: Sigma character-oriented communications subsystem simulator

   Copyright (c) 2007-2024, 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 substanXIAl 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.

   coc          7611 communications multiplexor

   17-Feb-24    RMS     Zero delay from SIO to INIT state (Ken Rector)
                        Detect and UEN on 0xFF order
   15-Dec-22    RMS     Moved SIO int pending test to devices
   24-Aug-22    RMS     Transmit long space is 0x6, not 0xD (Ken Rector)
                        Added LNORDER modifier
*/

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

/* Constants */

#define MUX_LINES       64                              /* max lines */
#define MUX_LINES_DFLT  8                               /* default lines */
#define MUX_INIT_POLL   8000
#define MUXL_WAIT       500
#define MUX_NUMLIN      mux_desc.lines                  /* curr # lines */

#define MUXC            0                               /* channel thread */
#define MUXI            1                               /* input thread */

/* Line status */

#define MUXL_XIA        0x01                            /* xmt intr armed */
#define MUXL_XIR        0x02                            /* xmt intr req */
#define MUXL_REP        0x04                            /* rcv enable pend */
#define MUXL_RBP        0x10                            /* rcv break pend */

/* Channel state */

#define MUXC_IDLE       0                               /* idle */
#define MUXC_INIT       1                               /* init */
#define MUXC_RCV        2                               /* receive */
#define MUXC_END        3                               /* end */

/* DIO address */

#define MUXDIO_V_FNC    0                               /* function */
#define MUXDIO_M_FNC    0xF
#define MUXDIO_V_COC    4                               /* ctlr num */
#define MUXDIO_M_COC    0xF
#define MUXDIO_GETFNC(x) (((x) >> MUXDIO_V_FNC) & MUXDIO_M_FNC)
#define MUXDIO_GETCOC(x) (((x) >> MUXDIO_V_COC) & MUXDIO_M_COC)

#define MUXDAT_V_LIN    0                               /* line num */
#define MUXDAT_M_LIN    (MUX_LINES - 1)
#define MUXDAT_V_CHR    8                               /* output char */
#define MUXDAT_M_CHR    0xFF
#define MUXDAT_GETLIN(x) (((x) >> MUXDAT_V_LIN) & MUXDAT_M_LIN)
#define MUXDAT_GETCHR(x) (((x) >> MUXDAT_V_CHR) & MUXDAT_M_CHR)

uint8 mux_rbuf[MUX_LINES];                              /* rcv buf */
uint8 mux_xbuf[MUX_LINES];                              /* xmt buf */
uint8 mux_sta[MUX_LINES];                               /* status */
uint32 mux_tps = RTC_HZ_50;                             /* polls/second */
uint32 mux_scan = 0;                                    /* scanner */
uint32 mux_slck = 0;                                    /* scanner locked */
uint32 muxc_cmd = MUXC_IDLE;                            /* channel state */
uint32 mux_rint = INTV (INTG_E2, 0);
uint32 mux_xint = INTV (INTG_E2, 1);

static int32 mux_order[MUX_LINES] = { -1 };             /* line connection order */
TMLN mux_ldsc[MUX_LINES] = { 0 };                       /* line descrs */
TMXR mux_desc = { MUX_LINES_DFLT, 0, 0, mux_ldsc, mux_order }; /* mux descr */

extern uint32 chan_ctl_time;
extern uint32 CC;
extern uint32 *R;

uint32 mux_disp (uint32 op, uint32 dva, uint32 *dvst);
uint32 mux_dio (uint32 op, uint32 rn, uint32 ad);
uint32 mux_tio_status (void);
t_stat mux_chan_err (uint32 st);
t_stat muxc_svc (UNIT *uptr);
t_stat muxo_svc (UNIT *uptr);
t_stat muxi_rtc_svc (UNIT *uptr);
t_stat mux_reset (DEVICE *dptr);
t_stat mux_attach (UNIT *uptr, CONST char *cptr);
t_stat mux_detach (UNIT *uptr);
t_stat mux_vlines (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
void mux_reset_ln (int32 ln);
void mux_scan_next (t_bool clr);
t_stat muxi_put_char (uint32 c, uint32 ln);

/* MUX data structures

   mux_dev      MUX device descriptor
   mux_unit     MUX unit descriptor
   mux_reg      MUX register list
   mux_mod      MUX modifiers list
*/

dib_t mux_dib = { DVA_MUX, &mux_disp, DIO_MUX, &mux_dio };

UNIT mux_unit[] = {
    { UDATA (&muxc_svc, UNIT_ATTABLE, 0) },
    { UDATA (&muxi_rtc_svc, UNIT_DIS, 0) }
    };

REG mux_reg[] = {
    { BRDATA (STA, mux_sta, 16, 8, MUX_LINES) },
    { BRDATA (RBUF, mux_rbuf, 16, 8, MUX_LINES) },
    { BRDATA (XBUF, mux_xbuf, 16, 8, MUX_LINES) },
    { DRDATA (SCAN, mux_scan, 6) },
    { FLDATA (SLCK, mux_slck, 0) },
    { DRDATA (CMD, muxc_cmd, 2) },
    { DRDATA (TPS, mux_tps, 8), REG_HRO },
    { NULL }
    };

MTAB mux_mod[] = {
    { MTAB_XTD | MTAB_VDV, 1, NULL, "DISCONNECT",
      &tmxr_dscln, NULL, &mux_desc },
    { UNIT_ATT, UNIT_ATT, "summary", NULL,
      NULL, &tmxr_show_summ, (void *) &mux_desc },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "CONNECTIONS", NULL,
      NULL, &tmxr_show_cstat,  (void *) &mux_desc },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "STATISTICS", NULL,
      NULL, &tmxr_show_cstat,  (void *) &mux_desc },
    { MTAB_XTD|MTAB_VDV, 0, "CHAN", "CHAN",
      &io_set_dvc, &io_show_dvc, NULL },
    { MTAB_XTD|MTAB_VDV, 0, "DVA", "DVA",
      &io_set_dva, &io_show_dva, NULL },
    { MTAB_XTD | MTAB_VDV, 0, "LINES", "LINES",
      &mux_vlines, &tmxr_show_lines, (void *) &mux_desc },
    { MTAB_XTD | MTAB_VDV | MTAB_NMO,  0, "LINEORDER",   "LINEORDER",
      &tmxr_set_lnorder, &tmxr_show_lnorder, (void *) &mux_desc },
    { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "CSTATE", NULL,
      NULL, &io_show_cst, NULL },
    { MTAB_XTD|MTAB_VDV|MTAB_NMO, RTC_COC, "POLL", "POLL",
      &rtc_set_tps, &rtc_show_tps, (void *) &mux_tps },
    { 0 }
    };

DEVICE mux_dev = {
    "MUX", mux_unit, mux_reg, mux_mod,
    2, 10, 31, 1, 16, 8,
    &tmxr_ex, &tmxr_dep, &mux_reset,
    NULL, &mux_attach, &mux_detach,
    &mux_dib, DEV_MUX | DEV_DISABLE
    };

/* MUXL data structures

   muxl_dev     MUXL device descriptor
   muxl_unit    MUXL unit descriptor
   muxl_reg     MUXL register list
   muxl_mod     MUXL modifiers list
*/

UNIT muxl_unit[] = {
    { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT },
    { UDATA (&muxo_svc, TT_MODE_UC|UNIT_DIS, 0), MUXL_WAIT }
    };

MTAB muxl_mod[] = {
    { TT_MODE, TT_MODE_UC, "UC", "UC", NULL },
    { TT_MODE, TT_MODE_7B, "7b", "7B", NULL },
    { TT_MODE, TT_MODE_8B, "8b", "8B", NULL },
    { TT_MODE, TT_MODE_7P, "7p", "7P", NULL },
    { MTAB_XTD|MTAB_VUN, 0, NULL, "DISCONNECT",
      &tmxr_dscln, NULL, &mux_desc },
    { MTAB_XTD|MTAB_VUN|MTAB_NC, 0, "LOG", "LOG",
      &tmxr_set_log, &tmxr_show_log, &mux_desc },
    { MTAB_XTD|MTAB_VUN|MTAB_NC, 0, NULL, "NOLOG",
      &tmxr_set_nolog, NULL, &mux_desc },
    { 0 }
    };

REG muxl_reg[] = {
    { URDATA (TIME, muxl_unit[0].wait, 10, 24, 0,
              MUX_LINES, REG_NZ + PV_LEFT) },
    { NULL }
    };

DEVICE muxl_dev = {
    "MUXL", muxl_unit, muxl_reg, muxl_mod,
    MUX_LINES, 10, 31, 1, 8, 8,
    NULL, NULL, &mux_reset,
    NULL, NULL, NULL,
    NULL, 0
    };

/* MUX: IO dispatch routine */

uint32 mux_disp (uint32 op, uint32 dva, uint32 *dvst)
{
switch (op) {                                           /* case on op */

    case OP_SIO:                                        /* start I/O */
        *dvst = mux_tio_status ();                      /* get status */
        if (chan_chk_dvi (dva))                         /* int pending? */
              *dvst |= (CC2 << DVT_V_CC);               /* SIO fails */
          else if ((*dvst & DVS_CST) == 0) {            /* ctrl idle? */
            muxc_cmd = MUXC_INIT;                       /* start dev thread */
            sim_activate (&mux_unit[MUXC], 0);
            }
        break;

    case OP_TIO:                                        /* test status */
        *dvst = mux_tio_status ();                      /* return status */
        break;

    case OP_TDV:                                        /* test status */
        *dvst = 0;                                      /* no status */
        break;

    case OP_HIO:                                        /* halt I/O */
        *dvst = mux_tio_status ();                      /* get status */
        muxc_cmd = MUXC_IDLE;                           /* stop dev thread */
        sim_cancel (&mux_unit[MUXC]);
        io_sclr_req (mux_rint, 0);                      /* clr rcv int */
        io_sclr_req (mux_xint, 0);
        break;

    case OP_AIO:                                        /* acknowledge int */
        *dvst = 0;                                      /* no status */
        break;

    default:
        *dvst = 0;
        return SCPE_IERR;
        }

return 0;
}

/* MUX: DIO dispatch routine

   Although only 9 of 16 function codes are documented, schematics
   indicate that there are don't cares in decoding.

   0000         sense receiver status
   0001         turn receiver on
   0010         turn receiver off
   0011         turn receiver DSR off
   0100         sense transmitter status
   0101         transmit character
   0110         transmit long space (based on software)
   0111         turn transmitter DSR off
   1000         unused (equivalent to 0000)
   1001         unused
   1010         unused
   1011         unused
   1100         unused (equivalent to 0100)
   1101         transmit long space (in documentation; not correct)
   1110         turn transmitter off
   1111         unused
*/

uint32 mux_dio (uint32 op, uint32 rn, uint32 ad)
{
int32 ln;
uint32 fnc = MUXDIO_GETFNC (ad);
uint32 coc = MUXDIO_GETCOC (ad);

if (op == OP_RD) {                                      /* read direct */
    if (coc != 0)                                       /* nx COC? */
        return 0;
    R[rn] = mux_scan | 0x40;                            /* return line num */
    mux_sta[mux_scan] &= ~MUXL_XIR;                     /* clear int req */
    return 0;
    }
ln = MUXDAT_GETLIN (R[rn]);                             /* get line num */
if ((fnc & 0x4) != 0) {                                 /* transmit */
    if ((coc != 0) ||                                   /* nx COC or */
        (ln >= MUX_NUMLIN)) {                           /* nx line? */
        CC |= CC4;
        return 0;
        }
    switch (fnc) {                                      /* case on fnc */
    case 0x5: case 0x6:                                 /* xmit, xmit long space */
        if (fnc == 0x5)                                 /* send char? */
            mux_xbuf[ln] = MUXDAT_GETCHR (R[rn]);       /* get char */
        else mux_xbuf[ln] = 0;                          /* just space */
        sim_activate (&muxl_unit[ln], muxl_unit[ln].wait);
        mux_sta[ln] = (mux_sta[ln] | MUXL_XIA) & ~MUXL_XIR;
        mux_scan_next (1);                              /* unlock scanner */
        break;
    case 0xE:                                           /* stop transmit */
        mux_sta[ln] &= ~MUXL_XIA|MUXL_XIR;              /* disable int */
        mux_scan_next (1);                              /* unlock scanner */
        break;
    case 0x7:                                           /* disconnect */
        tmxr_reset_ln (&mux_ldsc[ln]);                  /* reset line */
        mux_reset_ln (ln);                              /* reset state */
    default:                                            /* fall through */
        break;
        }
    CC = (sim_is_active (&muxl_unit[ln])? 0: CC4) |
        (mux_ldsc[ln].conn? CC3: 0);
    }
else {                                                  /* receive */
    if ((coc != 0) ||                                   /* nx COC or */
        (ln >= MUX_NUMLIN))                             /* nx line */
        return 0;
    switch (fnc) {                                      /* case on fnc */
    case 0x1:                                           /* set rcv enable */
        if (mux_ldsc[ln].conn)                          /* connected? */
            mux_ldsc[ln].rcve = 1;                      /* just enable */
        else mux_sta[ln] |= MUXL_REP;                   /* enable pending */
        break;
    case 0x2:                                           /* clr rcv enable */
        mux_ldsc[ln].rcve = 0;
        mux_sta[ln] &= ~MUXL_REP;
        break;
    case 0x3:                                           /* disconnect */
        tmxr_reset_ln (&mux_ldsc[ln]);                  /* reset line */
        mux_reset_ln (ln);                              /* reset state */
    default:                                            /* fall through */
        break;
        }
    if (mux_sta[ln] & MUXL_RBP)                         /* break pending? */
        CC = CC3|CC4;
    else if (mux_ldsc[ln].conn)
        CC = mux_ldsc[ln].rcve? CC4: CC3;
    else CC = 0;
    }
return 0;
}    

/* Unit service - channel overhead

   The documentation says that the channel command is thrown away,
   but the system exerciser requires a UEN on command 0xFF.
*/

t_stat muxc_svc (UNIT *uptr)
{
uint32 st;
uint32 cmd;

if (muxc_cmd == MUXC_INIT) {                            /* init state? */
    st = chan_get_cmd (mux_dib.dva, &cmd);              /* get command */
    if (CHS_IFERR (st))                                 /* channel error? */
       mux_chan_err (st);                               /* go idle */
    else muxc_cmd = MUXC_RCV;                           /* no, receive */
    if (cmd == 0xFF) {                                  /* invalid cmd? */
        chan_uen (mux_dib.dva);                         /* uend */
        return SCPE_OK;
        }
    }
else if (muxc_cmd == MUXC_END) {                        /* end state? */
    st = chan_end (mux_dib.dva);                        /* set channel end */
    if (CHS_IFERR (st))                                 /* channel error? */
        mux_chan_err (st);                              /* go idle */
    else if (st == CHS_CCH) {                           /* command chain? */
        muxc_cmd = MUXC_INIT;                           /* restart thread */
        sim_activate (uptr, chan_ctl_time);             /* schedule soon */
        }
    else muxc_cmd = MUXC_IDLE;                          /* else idle */
    }
return SCPE_OK;
}

/* Unit service - polled input - called from rtc scheduler

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

t_stat muxi_rtc_svc (UNIT *uptr)
{
t_stat r;
int32 newln, ln, c;

if ((mux_unit[MUXC].flags & UNIT_ATT) == 0)             /* attached? */
    return SCPE_OK;
newln = tmxr_poll_conn (&mux_desc);                     /* look for connect */
if ((newln >= 0) && (mux_sta[newln] & MUXL_REP)) {      /* rcv enb pending? */
    mux_ldsc[newln].rcve = 1;                           /* enable rcv */
    mux_sta[newln] &= ~MUXL_REP;                        /* clr pending */
    }
tmxr_poll_rx (&mux_desc);                               /* poll for input */
for (ln = 0; ln < MUX_NUMLIN; ln++) {                   /* loop thru lines */
    if (mux_ldsc[ln].conn) {                            /* connected? */
        if ((c = tmxr_getc_ln (&mux_ldsc[ln]))) {       /* get char */
            if (c & SCPE_BREAK) {                       /* break? */
                mux_sta[ln] |= MUXL_RBP;                /* set rcv brk */
                c = 0;                                  /* space */
                ln |= 0x80;                             /* break flag */
                }
            else {                                      /* normal char */
                mux_sta[ln] &= ~MUXL_RBP;               /* clr rcv brk */
                c = sim_tt_inpcvt (c, TT_GET_MODE (muxl_unit[ln].flags));
                mux_rbuf[ln] = c;                       /* save char */
                }                                       /* end else char */
            if ((muxc_cmd == MUXC_RCV) &&               /* chan active? */
                (r = muxi_put_char (c, ln)))            /* char to chan */
                return r;
            }                                           /* end if char */
        }                                               /* end if conn */
    else mux_sta[ln] &= ~MUXL_RBP;                      /* disconnected */
    }                                                   /* end for */
return SCPE_OK;
}

/* Put character and line number in memory via channel */

t_stat muxi_put_char (uint32 c, uint32 ln)
{
uint32 st;

st = chan_WrMemB (mux_dib.dva, c);                      /* write char */
if (CHS_IFERR (st))                                     /* channel error? */
    return mux_chan_err (st);
st = chan_WrMemB (mux_dib.dva, ln);                     /* write line */
if (CHS_IFERR (st))                                     /* channel error? */
    return mux_chan_err (st);
if (st == CHS_ZBC) {                                    /* bc == 0? */
    muxc_cmd = MUXC_END;                                /* end state */
    sim_activate (&mux_unit[MUXC], chan_ctl_time);      /* quick schedule */
    }
io_sclr_req (mux_rint, 1);                              /* req ext intr */
return SCPE_OK;
}

/* Channel error */

t_stat mux_chan_err (uint32 st)
{
chan_uen (mux_dib.dva);                                 /* uend */
muxc_cmd = MUXC_IDLE;                                   /* go idle */
if (st < CHS_ERR)
    return st;
return 0;
}

/* Unit service - transmit side */

t_stat muxo_svc (UNIT *uptr)
{
int32 c;
uint32 ln = uptr - muxl_unit;                           /* line # */

if (mux_ldsc[ln].conn) {                                /* connected? */
    if (mux_ldsc[ln].xmte) {                            /* xmt enabled? */
        c = sim_tt_outcvt (mux_xbuf[ln], TT_GET_MODE (muxl_unit[ln].flags));
        if (c >= 0)
            tmxr_putc_ln (&mux_ldsc[ln], c);            /* output char */
        tmxr_poll_tx (&mux_desc);                       /* poll xmt */
        if (mux_sta[ln] & MUXL_XIA) {                   /* armed? */
            mux_sta[ln] |= MUXL_XIR;                    /* req intr */
            mux_scan_next (0);                          /* kick scanner */
            }
        }
    else {                                              /* buf full */
        tmxr_poll_tx (&mux_desc);                       /* poll xmt */
        sim_activate (uptr, muxl_unit[ln].wait);        /* wait */
        return SCPE_OK;
        }
    }
return SCPE_OK;
}

/* MUX status routine */

uint32 mux_tio_status (void)
{
if (muxc_cmd == MUXC_IDLE)                              /* idle? */
    return DVS_AUTO;
else return (DVS_AUTO|DVS_CBUSY|DVS_DBUSY|(CC2 << DVT_V_CC));
}

/* Kick scanner */

void mux_scan_next (t_bool clr)
{
int32 i;

if (clr)                                                /* unlock? */
    mux_slck = 0;
else if (mux_slck)                                      /* locked? */
    return;
for (i = 0; i < MUX_NUMLIN; i++) {                      /* scan lines */
    mux_scan = mux_scan + 1;                            /* next line */
    if (mux_scan >= (uint32) MUX_NUMLIN)
        mux_scan = 0;
    if (mux_sta[mux_scan] & MUXL_XIR) {                 /* flag set? */
        mux_slck = 1;                                   /* lock scanner */
        io_sclr_req (mux_xint, 1);                      /* req ext int */
        return;
        }
    }
return;
}

/* Reset routine */

t_stat mux_reset (DEVICE *dptr)
{
int32 i;

if (mux_dev.flags & DEV_DIS)                            /* master disabled? */
    muxl_dev.flags = muxl_dev.flags | DEV_DIS;          /* disable lines */
else muxl_dev.flags = muxl_dev.flags & ~DEV_DIS;
if (mux_unit[MUXC].flags & UNIT_ATT)                    /* master att? */
    rtc_register (RTC_COC, mux_tps, &mux_unit[MUXI]);   /* register timer */
else rtc_register (RTC_COC, RTC_HZ_OFF, NULL);          /* else dereg */
for (i = 0; i < MUX_LINES; i++)                         /* reset lines */
    mux_reset_ln (i);
return SCPE_OK;
}

/* Attach master unit */

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

r = tmxr_attach (&mux_desc, uptr, cptr);                /* attach */
if (r != SCPE_OK)                                       /* error */
    return r;
rtc_register (RTC_COC, mux_tps, &mux_unit[MUXC]);       /* register timer */
return SCPE_OK;
}

/* Detach master unit */

t_stat mux_detach (UNIT *uptr)
{
int32 i;
t_stat r;

r = tmxr_detach (&mux_desc, uptr);                      /* detach */
for (i = 0; i < MUX_LINES; i++)                         /* disable rcv */
    mux_reset_ln (i);
rtc_register (RTC_COC, RTC_HZ_OFF, NULL);               /* dereg */
return r;
}


/* Change number of lines */

t_stat mux_vlines (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
int32 newln, i, t;
t_stat r;

if (cptr == NULL)
    return SCPE_ARG;
newln = get_uint (cptr, 10, MUX_LINES, &r);
if ((r != SCPE_OK) || (newln == MUX_NUMLIN))
    return r;
if (newln == 0) return SCPE_ARG;
if (newln < MUX_NUMLIN) {
    for (i = newln, t = 0; i < MUX_NUMLIN; i++) t = t | mux_ldsc[i].conn;
    if (t && !get_yn ("This will disconnect users; proceed [N]?", FALSE))
            return SCPE_OK;
    for (i = newln; i < MUX_NUMLIN; i++) {
        if (mux_ldsc[i].conn) {
            tmxr_linemsg (&mux_ldsc[i], "\r\nOperator disconnected line\r\n");
            tmxr_reset_ln (&mux_ldsc[i]);               /* reset line */
            }
        muxl_unit[i].flags = muxl_unit[i].flags | UNIT_DIS;
        mux_reset_ln (i);
        }
    }
else {
    for (i = MUX_NUMLIN; i < newln; i++) {
        muxl_unit[i].flags = muxl_unit[i].flags & ~UNIT_DIS;
        mux_reset_ln (i);
        }
    }
MUX_NUMLIN = newln;
return SCPE_OK;
}

/* Reset an individual line */

void mux_reset_ln (int32 ln)
{
sim_cancel (&muxl_unit[ln]);
mux_sta[ln] = 0;
mux_rbuf[ln] = 0;
mux_xbuf[ln] = 0;
mux_ldsc[ln].rcve = 0;
return;
}
