/* nova_qty.c: NOVA multiplexor (QTY/ALM) simulator

   Copyright (c) 2000-2015, Robert M. Supnik
   Written by Bruce Ray and used with his gracious permission.

   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.

   qty          multiplexor: QTY = 4060, ALM = 42xx

   28-Mar-15    RMS     Revised to use sim_printf
   14-Mar-12    RMS     Fixed dangling else clauses
   04-Jul-07    BKR     Fixed QTY output line number calculation (affected higher line numbers),
   25-Mar-04    RMS     Updated for V3.2
   12-Jan-04    BKR     Initial release
                        includes both original DG "quad" multiplexor (QTY)
                        and later Asynchronous Line Multiplexor (ALM) support.
*/


/*----------------------------------------------------------------------*/
/*                      QTY [4060-compatible] multiplexor               */
/*----------------------------------------------------------------------*/

/*
 *      Emulate the DG 4060 "quad" (QTY) serial port multiplexor.  DG modem
 *      control is not supported in this revision due to its obtuse nature
 *      of using a separate [semi-secret] device MDM which is actually part
 *      of the DG 4026/4027 multiplexor hardware(!).
 *      (Full modem support is provided in the ALM driver.)
 *
 *
 *      4060 Hardware
 *
 *      device code:    030 [primary],
 *                      070 [secondary]
 *      interrupt mask: B14 [000002]
 *      ASM mnemonic:   QTY
 *
 *
 *      4060 Input/Output Word Format:
 *
 *      _________________________________________________________________
 *      | RI| TI|        channel        |           character           |
 *      ----+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
 *         0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
 *
 *
 *              RI      - receiver interrupt
 *              TI      - transmitter interrupt
 *              channel - channel number, 0 - 63.
 *              character- character (valid if receiver interrupt, undefined if transmitter)
 *
 *      Notes:
 *
 *      Maximum 64 lines supported.
 *      DONE set whenever any received character fully assembled and ready,
 *              or when any output character transmitted and line is ready
 *              to accept next output character.
 *      BUSY set whenever output character is being sent on any line.
 *      Note that early 4060s did NOT have a busy flag!
 *      IORST clears device Done, no other user instruction does.
 *      IORST clears each line's individual R.I. and T.I.
 *
 *
 *      Instructions:
 *
 *      DIA     get multiplexor status word [format defined above]
 *      DOA     send character to QTY line [format defined above, RI & SI ]
 *      DIB     <ignored> [returns backplane bus noise]
 *      DOB     clear QTY line
 *      DIC     <ignored> [returns backplace bus noise]
 *      DOC     <ignored>
 *      'C'     clears global done, then checks for RI and TI;
 *      'P'     <ignored>
 *      'S'     <ignored>
 */


#include "nova_defs.h"

#include "sim_sock.h"
#include "sim_tmxr.h"


#define UNIT_V_8B   (UNIT_V_UF + 0)                     /* 8b output */
#define UNIT_8B     (1 << UNIT_V_8B)



extern int32    int_req, dev_busy, dev_done, dev_disable ;
extern int32    tmxr_poll ;                             /* calibrated delay */

t_stat  qty_setnl   ( UNIT * uptr, int32 val, CONST char * cptr, void * desc ) ;

t_stat  qty_attach  ( UNIT * uptr, CONST char * cptr ) ;
t_stat  qty_detach  ( UNIT * uptr ) ;
t_stat  qty_reset   ( DEVICE * dptr ) ;
t_stat  qty_svc     ( UNIT * uptr ) ;
int32   qty         ( int32 pulse, int32 code, int32 AC ) ;

t_stat  alm_reset   ( DEVICE * dptr ) ;
t_stat  alm_svc     ( UNIT * uptr ) ;
int32   alm         ( int32 pulse, int32 code, int32 AC ) ;

extern DEVICE  alm_dev ;


#define QTY_MAX     64                          /*  max number of QTY lines - hardware  */


int32   qty_brkio   = SCPE_OK ;                         /*  default I/O status code     */
int32   qty_max     = QTY_MAX ;                         /*  max # QTY lines - user      */
                                                        /*  controllable                */
int32   qty_mdm     = 0 ;                               /*  QTY modem control active?   */
int32   qty_auto    = 0 ;                               /*  QTY auto disconnect active? */
int32   qty_polls   = 0 ;                               /*  total 'qty_svc' polls       */


TMLN    qty_ldsc[ QTY_MAX ] = { {0} } ;                 /*  QTY line descriptors        */
TMXR    qty_desc    = { QTY_MAX, 0, 0, qty_ldsc } ;     /*  mux descriptor      */
int32   qty_status[ QTY_MAX ] = { 0 } ;                 /*  QTY line status             */
                                                        /*  (must be at least 32 bits)  */
int32   qty_tx_chr[ QTY_MAX ] = { 0 } ;                 /*  QTY line output character   */


/* QTY data structures

   qty_dev      QTY device descriptor
   qty_unit     QTY unit descriptor
   qty_reg      QTY register list
*/

DIB qty_dib = { DEV_QTY, INT_QTY, PI_QTY, &qty } ;

UNIT    qty_unit =
        {
        UDATA (&qty_svc, (UNIT_ATTABLE), 0)
        } ;

REG qty_reg[] =  /*  ('alm_reg' should be similar to this except for device code related items)  */
        {
        { ORDATA (BUF, qty_unit.buf, 8) },
        { FLDATA (BUSY, dev_busy, INT_V_QTY) },
        { FLDATA (DONE, dev_done, INT_V_QTY) },
        { FLDATA (DISABLE, dev_disable, INT_V_QTY) },
        { FLDATA (INT, int_req, INT_V_QTY) },

        { FLDATA (MDMCTL, qty_mdm,  0) },
        { FLDATA (AUTODS, qty_auto, 0) },
        { DRDATA (POLLS, qty_polls, 32) },
        { NULL }
        } ;

MTAB    qty_mod[] =
        {
        { UNIT_8B, 0, "7b", "7B", NULL },
        { UNIT_8B, UNIT_8B, "8b", "8B", NULL },
        { MTAB_XTD | MTAB_VDV, 1, NULL, "DISCONNECT",
            &tmxr_dscln, NULL, (void *)&qty_desc },
        { UNIT_ATT, UNIT_ATT, "connections", NULL,
          NULL, &tmxr_show_summ, (void *)&qty_desc },
        { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "CONNECTIONS", NULL,
            NULL, &tmxr_show_cstat, (void *)&qty_desc },
        { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "STATISTICS", NULL,
            NULL, &tmxr_show_cstat, (void *)&qty_desc },
        { MTAB_XTD | MTAB_VDV, 0, "LINES", "LINES",
            &qty_setnl, &tmxr_show_lines, (void *) &qty_desc },
        { 0 }
        } ;

DEVICE  qty_dev =
        {
        "QTY", &qty_unit, qty_reg, qty_mod,
        1, 10, 31, 1, 8, 8,
        NULL, NULL, &qty_reset,
        NULL, &qty_attach, &qty_detach,
        &qty_dib, (DEV_DISABLE | DEV_DIS | DEV_MUX)
        };

#define DG_RETURN( status, data )   (int32)(((status) << IOT_V_REASON) | ((data) & 0x0FFFF) )

/*
 *      QTY_S_xxx               QTY device status reference
 *      QTY_L_xxx               QTY line status word reference (qty_status[])
 */

        /*----------------------------------------------*/
        /*                QTY device status             */
        /*----------------------------------------------*/

#define QTY_S_RI        0x8000                          /*  Receiver Interrupt          */
#define QTY_S_TI        0x4000                          /*  Transmitter interrupt       */
#define QTY_S_LMASK     0x3F00                          /*  line mask                   */
#define QTY_S_DMASK     0x00FF                          /*  data mask (received char)   */



#define QTY_MASTER_ACTIVE( desc )   ( (desc)->master )

#define QTY_LINE_EXTRACT( x )       (((x) & QTY_S_LMASK) >> 8)

#define QTY_LINE_TX_CHAR( line )    qty_tx_chr[ ((line) % QTY_MAX) ]
#define QTY_LINE_RX_CHAR( line )    (qty_status[ (line) ] & QTY_S_DMASK)
#define QTY_UNIT_ACTIVE( unitp )    ( (unitp)->conn )

#define QTY_LINE_BITS( line, bits ) (qty_status[ (line) ] & bits)

#define QTY_LINE_SET_BIT(   line, bit )  qty_status[ (line) ] |=  (bit)  ;
#define QTY_LINE_CLEAR_BIT( line, bit )  qty_status[ (line) ] &= ~(bit)  ;
#define QTY_LINE_BIT_SET( line, bit )   (qty_status[ (line) ] &   (bit))


        /*----------------------------------------------*/
        /*                  QTY line status             */
        /*----------------------------------------------*/

#define QTY_L_RXE       0x800000                        /*  receiver enabled?           */
#define QTY_L_RXBZ      0x400000                        /*  receiver busy?              */
#define QTY_L_RXDN      0x200000                        /*  receiver done?              */
#define QTY_L_TXE       0x080000                        /*  transmitter enabled?        */
#define QTY_L_TXBZ      0x040000                        /*  transmitter busy?           */
#define QTY_L_TXDN      0x020000                        /*  transmitter done?           */

#define QTY_L_BREAK     0x008000                        /*  BREAK character received    */
#define QTY_L_RING      0x004000                        /*  Ring interrupt              */
#define QTY_L_CD        0x002000                        /*  Carrier Detect              */
#define QTY_L_DTR       0x001000                        /*  Data Terminal Ready         */
                                                        /*  <0x00FF = character>        */

#define QTY_L_LOOPBK    0x00010000                      /*  loopback mode               */
#define QTY_L_OVRERR    0x00020000                      /*  overrun error               */
#define QTY_L_FRMERR    0x00040000                      /*  framing error               */
#define QTY_L_PARERR    0x00080000                      /*  parity error                */


/* CD, CTS, DSR, RI */
                                                        /*  <future>  */

#define QTY_L_MODEM     0x0080                          /*  <not yet used>      */
#define QTY_L_TELNET    0x0040                          /*  <not yet used>      */
#define QTY_L_AUTODIS   0x0020                          /*  <not yet used>      */
#define QTY_L_PARITY
#define QTY_L_7BIT
#define QTY_L_BAUD                                      /*  <4 bits>            */


#define QTY_L_DMASK     0x000FF                         /*  data mask (always 8 bits)   */

/*  Note:  use at least an 'int32' for this guy  */

    /*------------------------------*/
    /*        qty_tmxr_putc         */
    /*------------------------------*/

int qty_tmxr_putc( int line, TMLN * lp, int kar )
    {
    int     a ;

    /*----------------------------------------------*/
    /*  Send character to given QTY/telnet line.    */
    /*                                              */
    /*  enter:      line    QTY line #              */
    /*              lp      Telnet unit def ptr     */
    /*              kar     character to send       */
    /*                                              */
    /*  return:     SCPE_OK                         */
    /*              SCPE_STALL                      */
    /*              SCPE_LOST                       */
    /*----------------------------------------------*/

    a = tmxr_putc_ln( lp, kar ) ;
    if ( a == SCPE_OK)
        {
        QTY_LINE_SET_BIT(   line, QTY_L_TXDN )
        QTY_LINE_CLEAR_BIT( line, QTY_L_TXBZ )
        }
    else if ( a == SCPE_STALL )
        {
        /*
         (should we try to output the buffer
         and then regroup...?)
         */
        QTY_LINE_SET_BIT(   line, QTY_L_TXBZ )
        QTY_LINE_CLEAR_BIT( line, QTY_L_TXDN )
        QTY_LINE_TX_CHAR( line ) = kar ;
        }
    else if ( a == SCPE_LOST )
        {
        /*  no connection - hangup?  */
        QTY_LINE_SET_BIT(   line, QTY_L_TXBZ )
        QTY_LINE_CLEAR_BIT( line, QTY_L_TXDN )
        QTY_LINE_TX_CHAR( line ) = kar ;
        }
    return ( a ) ;
    }   /*  end of 'qty_tmxr_putc'  */


    /*----------------------------------------------*/
    /*                 qty_update_rcvi              */
    /*----------------------------------------------*/

int qty_update_rcvi( TMXR * mp )
    {
    int     line ;
    TMLN *      lp ;
    int32       datum ;
    int     changes ;

    /*------------------------------------------------------*/
    /*  Search through connected telnet lines for any input */
    /*  activity.                                           */
    /*                                                      */
    /*  enter:      mp      master telnet qty desc ptr      */
    /*                                                      */
    /*  return:     int     change count (0 = none seen)    */
    /*------------------------------------------------------*/

    for ( changes = line = 0; line < mp->lines; ++line )
       if ( (lp=mp->ldsc+line)->conn && lp->rcve )
          if ( (datum=tmxr_getc_ln(lp)) )
        {
        if ( datum & SCPE_BREAK )
            {
            /*  what should we do here - set QTY_L_BREAK?  */
            datum = datum & 0x00FF ;
            }
        else
            {
            datum = datum & 0x00FF ;
            }
         /*  <check parity, masking, forced parity, CR/LF xlation>  */

        QTY_LINE_CLEAR_BIT( line, (QTY_L_RXBZ | QTY_L_DMASK) ) ;
        QTY_LINE_SET_BIT( line, (QTY_L_RXDN | datum) ) ;
        ++changes ;
        }
    return ( changes ) ;
    }   /*  end of 'qty_update_rcvi'  */


    /*----------------------------------------------*/
    /*                qty_update_xmti               */
    /*----------------------------------------------*/

int qty_update_xmti( TMXR * mp )
    {
    int     line ;
    TMLN *      lp ;
    int     changes ;

    /*------------------------------------------------------*/
    /*  Search through connected telnet lines for any de-   */
    /*  ferred output activity.                             */
    /*                                                      */
    /*  enter:      mp      master telnet qty desc ptr      */
    /*                                                      */
    /*  return:     int     change count (0 = none seen)    */
    /*------------------------------------------------------*/

    /*  any TX DONE flags set
     *  any TX BUSY flags set
     */

    for ( changes = line = 0; line < mp->lines; ++line )
       if ( QTY_LINE_BIT_SET(line,QTY_L_TXBZ) )
          if ( (lp=mp->ldsc+line)->conn && lp->xmte )
        {
        /*  why are we busy?  buffer was full?  */
        /*  now some space available - try
         *  to stuff pending character in
         *  buffer and free up the world
         */
        qty_tmxr_putc( line, lp, QTY_LINE_TX_CHAR(line) ) ;
        ++changes ;
        }
    return ( changes ) ;
    }   /*  end of 'qty_update_xmti'  */


    /*----------------------------------------------*/
    /*                qty_update_status             */
    /*----------------------------------------------*/

int qty_update_status( DIB * dibp, TMXR * tmxr_desc )
    {
    int     line ;
    int     status ;
    int     txbusy ;

    /*----------------------------------------------*/
    /*  return global device status for current qty */
    /*  state.                                      */
    /*                                              */
    /*  Receiver interrupts have higher priority    */
    /*  than transmitter interrupts according to DG */
    /*  but this routine could be modified to use   */
    /*  different priority criteria.                */
    /*                                              */
    /*  Round-robin polling could also be used in   */
    /*  some future release rather than starting    */
    /*  with line 0 each time.                      */
    /*                                              */
    /*  Return <QTY_S_RI + line # + character> of   */
    /*  first waiting character, else return        */
    /*  <QTY_S_TI + line #> of first finished line  */
    /*  output, else return 0.                      */
    /*                                              */
    /*  This routine does -not- clear input line    */
    /*  BZ/DN flags; caller should do this.         */
    /*                                              */
    /*  Global device done and busy flags are       */
    /*  updated.                    */
    /*----------------------------------------------*/

    for ( txbusy = status = line = 0 ; line < qty_max ; ++line )
        {
        txbusy |= (QTY_LINE_BIT_SET(line,QTY_L_TXBZ)) ;
        if ( QTY_LINE_BIT_SET(line,QTY_L_RXDN) )
            {
            if ( ! status )
                {
                status = QTY_LINE_BITS( line, QTY_S_DMASK ) | QTY_S_RI ;
                status = status | (line << 8) ;
                }
            break ;
            }
        else if ( QTY_LINE_BIT_SET(line,QTY_L_TXDN) )
            {
            if ( ! (status & QTY_S_RI) )
                if ( ! (status & QTY_S_RI) )
                {
                status = QTY_S_TI ;
                status = status | (line << 8) ;
                }
            }
        }
    /*  <we could check each line for TX busy to set DEV_SET_BUSY)?>  */
    DEV_CLR_BUSY( INT_QTY ) ;
    DEV_CLR_DONE( INT_QTY ) ;
    if ( txbusy )
        {
        DEV_SET_BUSY( INT_QTY ) ;
        }
    if ( status & (QTY_S_RI | QTY_S_TI) )
        {
        DEV_SET_DONE( INT_QTY ) ;
        }
    DEV_UPDATE_INTR ;                                   /*  update final intr status  */
        return ( status ) ;
    }   /*  end of 'qty_update_status'  */


    /*--------------------------------------------------------------*/
    /*                            qty_attach                        */
    /*--------------------------------------------------------------*/

t_stat qty_attach( UNIT * unitp, CONST char * cptr )
    {
    t_stat  r ;
    int a ;

    /*  switches:   A       auto-disconnect
     *              M       modem control
     */

    qty_mdm = qty_auto = 0;                             /* modem ctl off */
    r = tmxr_attach( &qty_desc, unitp, cptr ) ;         /* attach QTY */
    if ( r != SCPE_OK )
        {
        return ( r ) ;                                  /* error! */
        }
    if ( sim_switches & SWMASK('M') )                   /* modem control? */
        {
        qty_mdm = 1;
        sim_printf( "Modem control activated\n" ) ;
        if ( sim_switches & SWMASK ('A') )              /* autodisconnect? */
            {
            qty_auto = 1 ;
            sim_printf( "Auto disconnect activated\n" ) ;
            }
        }
    qty_polls = 0 ;
    for ( a = 0 ; a < QTY_MAX ; ++a )
        {
        /*  QTY lines are always enabled - force RX and TX to 'enabled' */
        qty_status[ a ] = (QTY_L_RXE | QTY_L_TXE) ;
        }
    sim_activate( unitp, tmxr_poll ) ;
    return ( SCPE_OK ) ;
    }   /*  end of 'qty_attach'  */


    /*--------------------------------------------------------------*/
    /*                            qty_detach                        */
    /*--------------------------------------------------------------*/

t_stat qty_detach( UNIT * unitp )
    {
    sim_cancel( unitp ) ;
    return ( tmxr_detach(&qty_desc,unitp) ) ;
    }   /*  end of 'qty_detach'  */


    /*--------------------------------------------------------------*/
    /*                             qty_clear                        */
    /*--------------------------------------------------------------*/

t_stat qty_clear( t_bool flag )
    {
    int line ;

    for ( line = 0 ; line < qty_max ; ++line )
        {
        qty_ldsc[line].xmte = 0 ;
        qty_ldsc[line].rcve = 0 ;
        if ( ! qty_ldsc[line].conn )
            {
            qty_ldsc[line].xmte = 1 ;                   /* set xmt enb */
            qty_ldsc[line].rcve = 1 ;                   /* clr rcv enb */
            }
        }
    return ( SCPE_OK ) ;
    }   /*  end of 'qty_clear'  */


    /*----------------------------------------------*/
    /*                 qty_common_reset             */
    /*----------------------------------------------*/

t_stat qty_common_reset( DIB * dibp, UNIT * unitp, DEVICE * dptr )
    {
    if ((dptr->flags & DEV_DIS) == 0)
        {
        if (dptr == &qty_dev) alm_dev.flags |= DEV_DIS;
        else qty_dev.flags |= DEV_DIS;
        }
    qty_clear( TRUE ) ;
    DEV_CLR_BUSY( INT_QTY ) ;                              /*  clear busy  */
    DEV_CLR_DONE( INT_QTY ) ;                              /*  clear done, int */
    DEV_UPDATE_INTR ;
    if ( QTY_MASTER_ACTIVE(&qty_desc) )
        {
        sim_activate( unitp, tmxr_poll ) ;
        }
    else
        {
        sim_cancel( unitp ) ;
        }
    return ( SCPE_OK ) ;
    }   /*  end of 'qty_common_reset'  */


    /*--------------------------------------------------------------*/
    /*                            qty_reset                         */
    /*--------------------------------------------------------------*/

t_stat qty_reset( DEVICE * dptr )
    {
    return ( qty_common_reset(&qty_dib,&qty_unit,dptr) ) ;
    }   /*  end of 'qty_reset'  */


/* Unit service routine

   The QTY/ALM polls to see if asynchronous activity has occurred and now
   needs to be processed.  The polling interval is controlled by the clock
   simulator, so for most environments, it is calibrated to real time.

   The simulator assumes that software enables all of the multiplexors,
   or none of them.
*/

    /*----------------------------------------------*/
    /*                  qty_common_svc              */
    /*----------------------------------------------*/

t_stat qty_common_svc( DIB * dibp, UNIT * unitp )
    {
    int     line ;
    int     newln ;
    TMLN *      tmlnp ;

    ++qty_polls ;                                       /*  another time 'round the track  */
    newln = tmxr_poll_conn( &qty_desc ) ;               /*  anybody knocking at the door?  */
    if ( (newln >= 0) && qty_mdm )
        {
        if ( newln >= qty_max )
            {
            return SCPE_IERR;                               /*  WTF - sanity check failed, over?  */
            }
        else
            {
            line = newln ;                                  /*  handle modem control  */
            tmlnp =&qty_ldsc[ line ] ;
            tmlnp->rcve = tmlnp->xmte = 1 ;
            /*  do QTY_LINE_ bit fiddling and state machine
             *  manipulation with modem control signals
             */
            }
        }

    tmxr_poll_rx( &qty_desc ) ;                         /*  poll input                          */
    qty_update_rcvi( &qty_desc ) ;                      /*  update receiver interrupt status    */

    tmxr_poll_tx( &qty_desc ) ;                         /*  poll output                         */
    qty_update_xmti( &qty_desc ) ;                      /*  update transmitter interrupt status */

    qty_update_status( dibp, &qty_desc ) ;              /*  update device status                */

    sim_activate( unitp, tmxr_poll ) ;                  /*  restart the bubble machine          */
    return ( SCPE_OK ) ;
    }                                                   /*  end of 'qty_common_svc'  */


    /*--------------------------------------------------------------*/
    /*                            qty_svc                           */
    /*--------------------------------------------------------------*/

t_stat qty_svc( UNIT * uptr )
    {
    return ( qty_common_svc(&qty_dib,uptr) ) ;
    }                                                   /*  end of 'qty_svc'  */


    /*--------------------------------------------------------------*/
    /*                              qty                             */
    /*--------------------------------------------------------------*/

int32 qty( int32 pulse, int32 code, int32 AC )
    {
    int32       iodata ;
    int32       ioresult ;
    int     line ;
    TMLN *      tmlnp ;
    int     a ;
    int     kar ;

    /*--------------------------------------------------------------*/
    /*  DG 4060[-compatible] "quad" multiplexor instruction handler */
    /*--------------------------------------------------------------*/

    ioresult= qty_brkio ;   /*  (assume returning I/O break value   */
    iodata  = 0 ;           /*  (assume 16-bit Nova/Eclipse bus)    */
    switch ( code )
        {
    case ioNIO :    /*  <no operation>  */
        break ;

    case ioDIA :    /*  get current QTY status  */
        iodata = qty_update_status( &qty_dib, &qty_desc ) ;
        if ( iodata & QTY_S_RI )
            {                                           /*  clear line's input buffer  */
            QTY_LINE_CLEAR_BIT( (QTY_LINE_EXTRACT(iodata)), (QTY_L_RXBZ | QTY_L_RXDN) )
            /*
            character masking ;
            parity checking ;
            parity generating ;
             */
            }
        qty_update_status( &qty_dib, &qty_desc ) ;
        break ;

    case ioDOA :    /*  send character to QTY  */
        line = QTY_LINE_EXTRACT( AC ) ;
        if ( line < qty_max )
            if ( QTY_LINE_BIT_SET(line,QTY_L_TXE) )
            {
            /*
            perform any character translation:
            7 bit/ 8 bit
            parity generation
             */
            kar = AC & ((qty_unit.flags & UNIT_8B)? 0377: 0177) ;
            /*  do any parity calculations also  */

            tmlnp = &qty_ldsc[ line ] ;
            a = qty_tmxr_putc( line, tmlnp, kar ) ;
            if ( a != SCPE_OK)
                {
                /*  do anything at this point?  */
                }
            qty_update_status( &qty_dib, &qty_desc ) ;
            }
        break ;

    case ioDIB :    /*  no QTY function - return bus noise in AC  */
        break ;

    case ioDOB :    /*  clear QTY output channel busy and done flag  */
        QTY_LINE_CLEAR_BIT( (QTY_LINE_EXTRACT(AC)), (QTY_L_TXBZ | QTY_L_TXDN) )
        qty_update_status( &qty_dib, &qty_desc ) ;
        break ;

    case ioDIC :    /*  no QTY function - return bus noise in AC  */
        break ;

    case ioDOC :    /*  no QTY function - ignore  */
        break ;

    case ioSKP :    /*  I/O skip test - should never come here  */
        break ;

    default :
        /*  <illegal I/O operation value>  */
        break ;
        }

    switch ( pulse )
        {
    case iopN :     /*  <ignored (of course)>  */
        break ;

    case iopS :     /*  <ignored>  */
        break ;

    case iopP :     /*  <ignored>  */
        break ;

    case iopC :
        qty_update_status( &qty_dib, &qty_desc ) ;
        break ;

    default :
        /*  <illegal pulse value>  */
        break ;
        }

    return ( DG_RETURN( ioresult, iodata ) ) ;
    }   /*  end of 'qty'  */

    /*--------------------------------------------------------------*/
    /*                             qty_setnl                        */
    /*--------------------------------------------------------------*/

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

    t_stat  r ;
    if ( cptr == NULL )
        {
        return ( SCPE_ARG ) ;
        }
    newln = (int32) get_uint( cptr, 10, QTY_MAX, &r ) ;
    if ( (r != SCPE_OK) || (newln == qty_desc.lines) )
        {
        return ( r ) ;
        }
    if ( (newln == 0) || (newln > QTY_MAX) )
        {
        return ( SCPE_ARG ) ;
        }
    if ( newln < qty_desc.lines )
        {
        for ( i = newln, t = 0 ; i < qty_desc.lines ; ++i )
            {
            t = t | qty_ldsc[i].conn ;
            }
        if ( t && ! get_yn("This will disconnect users; proceed [N]?", FALSE) )
            {
            return ( SCPE_OK ) ;
            }
        for ( i = newln ; i < qty_desc.lines ; ++i )
            {
            if ( qty_ldsc[i].conn )
                {                                       /* reset line */
                tmxr_msg( qty_ldsc[i].conn, "\r\nOperator disconnected line\r\n" ) ;
                tmxr_reset_ln( &qty_ldsc[i] ) ;
                }
            qty_clear( TRUE ) ;                         /* reset mux */
            }
        }
    qty_max = qty_desc.lines = newln ;
        /*  Huh, I don't understand this yet...
        qty_max = ((qty_dev.flags & DEV_DIS)? 0 : (qty_desc.lines / QTY_MAX)) ;
         */
    return ( SCPE_OK ) ;
    }   /*  end of 'qty_setnl'  */


/*----------------------------------------------------------------------*/
/*                       ALM [425x-compatible] multiplexor              */
/*----------------------------------------------------------------------*/

/*
 *      device code:    034 [primary],
 *                      074 [secondary]
 *      interrupt mask: B14 [000002]
 *      ASM mnemonic:   ALM
 *
 *      ALM [4255-4258] I/O instructions
 *
 *      DIA     read line and section requesting service
 *      DOA     select line and section (lines 0-255, 8-bits) + rcvr/xmit
 *      DIB     receive data
 *      DOB     00  transmit data
 *              01  transmit BREAK
 *              10  set modem control status
 *              11  <ignored>
 *      DIC     read receiver or modem status
 *      DOC     00  control line section and diag mode
 *              01  
 *              10  specify line characteristics
 *              11
 *
 *      undocumented DG "features":
 *
 *              NIOS sets board offline
 *              NIOC sets board online
 *              Modem control signal state change can signal interrupt
 *              explicit line select with DOA
 *              implicit line select with DIA
 *
 *      We support 64 lines maximum in this release although some ALM's could
 *      theoretically support up to 256.
 */


DIB alm_dib = { DEV_ALM, INT_ALM, PI_ALM, &alm } ;
UNIT    alm_unit =
        {
        UDATA (&alm_svc, (UNIT_ATTABLE), 0)
        } ;

REG alm_reg[] =  /*  ('qty_reg' should be similar to this except for device code related items)  */
        {
        { ORDATA (BUF, alm_unit.buf, 8) },
        { FLDATA (BUSY, dev_busy, INT_V_ALM) },
        { FLDATA (DONE, dev_done, INT_V_ALM) },
        { FLDATA (DISABLE, dev_disable, INT_V_ALM) },
        { FLDATA (INT, int_req, INT_V_ALM) },

        { FLDATA (MDMCTL, qty_mdm,  0) },
        { FLDATA (AUTODS, qty_auto, 0) },
        { DRDATA (POLLS, qty_polls, 32) },
        { NULL }
        } ;

DEVICE  alm_dev =
        {
        "ALM", &alm_unit, alm_reg, qty_mod,
        1, 10, 31, 1, 8, 8,
        NULL, NULL, &alm_reset,
        NULL, &qty_attach, &qty_detach,
        &alm_dib, (DEV_DISABLE | DEV_MUX)
        } ;

int alm_section     = -1 ;      /*  current line "section" (0 = RCV, 1 = XMT)  */
int alm_line        = -1 ;      /*  current line [0-63]                 */
int alm_diag_mode   =  0 ;      /*  <not yet supported>                 */
int alm_line_mask   = 0x003F ;  /*  maximum of 64 lines in this rev     */


#define ALM_LINE_EXTRACT( x )       (((x) >> 1) & alm_line_mask)
#define ALM_SECT_EXTRACT( x )       ((x) & 0x0001)


    /*--------------------------------------------------------------*/
    /*                            alm_reset                         */
    /*--------------------------------------------------------------*/

t_stat alm_reset( DEVICE * dptr )
    {
    return ( qty_common_reset(&alm_dib,&alm_unit,dptr) ) ;
    }   /*  end of 'alm_reset'  */


    /*--------------------------------------------------------------*/
    /*                            alm_svc                           */
    /*--------------------------------------------------------------*/

t_stat alm_svc( UNIT * uptr )
    {
    return ( qty_common_svc(&alm_dib,uptr) ) ;
    }   /*  end of 'alm_svc'  */


    /*--------------------------------------------------------------*/
    /*                              alm                             */
    /*--------------------------------------------------------------*/

int32 alm( int32 pulse, int32 code, int32 AC )
    {
    int32       iodata ;
    int32       ioresult ;
    TMLN *      tmlnp ;
    int     a ;
    int     kar ;

    /*--------------------------------------------------------------*/
    /*  DG 425x[-compatible] "ALM" multiplexor instruction handler  */
    /*--------------------------------------------------------------*/

    ioresult= qty_brkio ;   /*  (assume returning I/O break value   */
    iodata  = 0 ;           /*  (assume 16-bit Nova/Eclipse bus)    */
    switch ( code )
        {
    case ioNIO :    /*  <no operation>  */
        break ;

    case ioDIA :    /*  read line and section requesting service  */
        iodata = qty_update_status( &alm_dib, &qty_desc ) ;
        alm_line = (QTY_LINE_EXTRACT(iodata) & alm_line_mask) ;
        /*  (mask with 'alm_line_mask' in case ALM mask is different than QTY */
        alm_section = 0 ;
        if ( ! ( iodata & QTY_S_RI) )
          if ( iodata & QTY_S_TI )
            {
            alm_section = 1 ;                           /*  receiver quiet - transmitter done  */
            }
        iodata = (alm_line << 1) | alm_section ;
        break ;

    case ioDOA :    /*  set line and section  */
        alm_section = ALM_SECT_EXTRACT( AC ) ;
        alm_line    = ALM_LINE_EXTRACT( AC ) ;
        break ;

    case ioDIB :    /*  no ALM function - return bus noise in AC  */
        if ( alm_line < qty_max )
            {
            iodata = QTY_LINE_RX_CHAR( alm_line ) ;
            }
        break ;

    case ioDOB :    /*  output and modem control functions  */
        switch ( (AC >> 14) & 03 )
            {
        case 00 :   /*  transmit data  */
            if ( alm_line < qty_max )
                if ( QTY_LINE_BIT_SET(alm_line,QTY_L_TXE) )
                {
                /*
                perform any character translation:
                7 bit/ 8 bit
                parity generation
                 */
                kar = AC & ((alm_unit.flags & UNIT_8B)? 0377: 0177) ;
                /*  do any parity calculations also  */

                tmlnp = &qty_ldsc[ alm_line ] ;
                a = qty_tmxr_putc( alm_line, tmlnp, kar ) ;
                if ( a != SCPE_OK)
                    {
                    /*  do anything at this point?  */
                    }
                qty_update_status( &alm_dib, &qty_desc ) ;
                }
            break ;

        case 01 :   /*  transmit break  */
            if ( alm_line < qty_max )
                if ( QTY_LINE_BIT_SET(alm_line,QTY_L_TXE) )
                {
                tmlnp = &qty_ldsc[ alm_line ] ;
                /*
                a = qty_tmxr_putc( alm_line, tmlnp, kar ) ;
                if ( a != SCPE_OK)
                    {
                    }
                 */
                qty_update_status( &alm_dib, &qty_desc ) ;
                }
            break ;

        case 02 :   /*  set modem control status  */
            break ;

        case 03 :   /*  unused  */
            break ;
            }
        break ;

    case ioDIC :    /*  get modem or receiver status  */
        if ( alm_line < qty_max )
            {
            if ( alm_section )
                {
                /*  get modem section status  */
                if ( qty_ldsc[ alm_line ].xmte )
                    {
                    iodata = 0035 ;                     /*  set CD, CTS, DSR, MDM flags  */
                    }
                }
            else
                {
                /*  get receiver section status  */
                iodata = 0 ;                            /*  receiver error status - no errors by default  */
                }
            }
        break ;

    case ioDOC :    /*  set line attributes  */
        switch ( (AC >> 14) & 03 )
            {
        case 00 :   /*  control line section  */
            break ;

        case 01 :   /*  unused  */
            break ;

        case 02 :   /*  set line characteristics  */
            break ;

        case 03 :   /*  unused  */
            break ;
            }
        break ;

    case ioSKP :    /*  I/O skip test - should never come here  */
        break ;

    default :
        /*  <illegal I/O operation value>  */
        break ;
        }

    switch ( pulse )
        {
    case iopN : /*  <ignored (of course)>  */
        break ;

    case iopS : /*  set device busy
                 *  set all lines on board offline
                 *  clear each line's done
                 *  clear internal system
                 *  clear device busy
                 */
        for ( a = 0 ; a < qty_max ; ++a )
          if ( 1 /* (not yet optimized) */ )
            {
            QTY_LINE_CLEAR_BIT( a, (QTY_L_RXBZ | QTY_L_RXDN | QTY_L_TXBZ | QTY_L_TXDN) ) ;
            }
        qty_update_status( &alm_dib, &qty_desc ) ;
        break ;

    case iopP : /*  stop clock for all boards in off-line mode  */
        break ;

    case iopC :
        for ( a = 0 ; a < qty_max ; ++a )
          if ( 1 /* (not yet optimized) */ )
            {
            QTY_LINE_CLEAR_BIT( a, (QTY_L_RXBZ | QTY_L_RXDN | QTY_L_TXBZ | QTY_L_TXDN) ) ;
            }
        qty_update_status( &alm_dib, &qty_desc ) ;
        break ;

    default :
        /*  <illegal pulse value>  */
        break ;
        }

    return ( DG_RETURN( ioresult, iodata ) ) ;
    }   /*  end of 'alm'  */
