/* pdp11_dup.c: PDP-11 DUP11/DPV11 bit synchronous interface

   Copyright (c) 2013, Mark Pizzolato

   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.

   dup          DUP11 Unibus/DPV11 Qbus bit synchronous interface

   This module implements a bit synchronous interface to support DDCMP.  Other
   synchronous protocols which may have been supported on the DUP11/DPV11 bit 
   synchronous interface are explicitly not supported.

   Connections are modeled with a tcp session with connection management and 
   I/O provided by the tmxr library.

   The wire protocol implemented is native DDCMP WITHOUT the DDCMP SYNC 
   characters both initially and between DDCMP packets.

   15-May-13    MP      Initial implementation

*/

#if defined (VM_PDP10)                                  /* PDP10 version */
#include "pdp10_defs.h"

#elif defined (VM_VAX)                                  /* VAX version */
#include "vax_defs.h"

#else                                                   /* PDP-11 version */
#include "pdp11_defs.h"
#endif

#include "sim_tmxr.h"
#include "pdp11_ddcmp.h"
#include "pdp11_dup.h"

#if !defined(DUP_LINES)
#define DUP_LINES 8
#endif
#define INITIAL_DUP_LINES 1

#define DUP_WAIT   50           /* Minimum character time */
#define DUP_CONNECT_POLL    2   /* Seconds */

extern int32 IREQ (HLVL);
extern int32 tmxr_poll;                                 /* calibrated delay */
extern int32 clk_tps;                                   /* clock ticks per second */
extern int32 tmr_poll;                                  /* instructions per tick */

static uint16 dup_rxcsr[DUP_LINES];
static uint16 dup_rxdbuf[DUP_LINES];
static uint16 dup_parcsr[DUP_LINES];
static uint16 dup_txcsr[DUP_LINES];
static uint16 dup_txdbuf[DUP_LINES];
static t_bool dup_W3[DUP_LINES];
static t_bool dup_W5[DUP_LINES];
static t_bool dup_W6[DUP_LINES];
static t_bool dup_kmc[DUP_LINES];                       /* being used by a kmc or other internal simulator device */
static uint32 dup_rxi = 0;                                     /* rcv interrupts */
static uint32 dup_txi = 0;                                     /* xmt interrupts */
static uint32 dup_wait[DUP_LINES];                             /* rcv/xmt byte delay */
static uint32 dup_speed[DUP_LINES];                            /* line speed (bits/sec) */
static uint8 *dup_rcvpacket[DUP_LINES];                        /* rcv buffer */
static uint16 dup_rcvpksize[DUP_LINES];                        /* rcv buffer size */
static uint16 dup_rcvpkbytes[DUP_LINES];                       /* rcv buffer size of packet */
static uint16 dup_rcvpkinoff[DUP_LINES];                       /* rcv packet in offset */
static uint8 *dup_xmtpacket[DUP_LINES];                        /* xmt buffer */
static uint16 dup_xmtpksize[DUP_LINES];                        /* xmt buffer size */
static uint16 dup_xmtpkoffset[DUP_LINES];                      /* xmt buffer offset */
static uint32 dup_xmtpkstart[DUP_LINES];                       /* xmt packet start time */
static uint16 dup_xmtpkbytes[DUP_LINES];                       /* xmt packet size of packet */
static uint16 dup_xmtpkdelaying[DUP_LINES];                    /* xmt packet speed delaying completion */
static int32 dup_corruption[DUP_LINES];                       /* data corrupting troll hunger value */

static PACKET_DATA_AVAILABLE_CALLBACK dup_rcv_packet_data_callback[DUP_LINES];
static PACKET_TRANSMIT_COMPLETE_CALLBACK dup_xmt_complete_callback[DUP_LINES];
static MODEM_CHANGE_CALLBACK dup_modem_change_callback[DUP_LINES];

static t_stat dup_rd (int32 *data, int32 PA, int32 access);
static t_stat dup_wr (int32 data, int32 PA, int32 access);
static t_stat dup_set_modem (int32 dup, int32 rxcsr_bits);
static t_stat dup_get_modem (int32 dup);
static t_stat dup_svc (UNIT *uptr);
static t_stat dup_poll_svc (UNIT *uptr);
static t_stat dup_rcv_byte (int32 dup);
static t_stat dup_reset (DEVICE *dptr);
static t_stat dup_attach (UNIT *uptr, CONST char *ptr);
static t_stat dup_detach (UNIT *uptr);
static t_stat dup_clear (int32 dup, t_bool flag);
static int32 dup_rxinta (void);
static int32 dup_txinta (void);
static void dup_update_rcvi (void);
static void dup_update_xmti (void);
static void dup_clr_rxint (int32 dup);
static void dup_set_rxint (int32 dup);
static void dup_clr_txint (int32 dup);
static void dup_set_txint (int32 dup);
static t_stat dup_setnl (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
static t_stat dup_setspeed (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
static t_stat dup_showspeed (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
static t_stat dup_setcorrupt (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
static t_stat dup_showcorrupt (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
static t_stat dup_set_W3 (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
static t_stat dup_show_W3 (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
static t_stat dup_set_W5 (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
static t_stat dup_show_W5 (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
static t_stat dup_set_W6 (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
static t_stat dup_show_W6 (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
static uint16 dup_crc_ccitt (uint8 const *bytes, uint32 len);
static t_stat dup_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
static t_stat dup_help_attach (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
static const char *dup_description (DEVICE *dptr);

/* RXCSR - 16XXX0 - receiver control/status register */

static BITFIELD dup_rxcsr_bits[] = {
    BIT(BDATSET),                           /* Data Set Change B */
#define RXCSR_V_BDATSET 0
#define RXCSR_M_BDATSET (1<<RXCSR_V_BDATSET)
    BIT(DTR),                               /* Data Terminal Ready */
#define RXCSR_V_DTR     1
#define RXCSR_M_DTR     (1<<RXCSR_V_DTR)
    BIT(RTS),                               /* Request To Send */
#define RXCSR_V_RTS     2
#define RXCSR_M_RTS     (1<<RXCSR_V_RTS)
    BIT(SECXMT),                            /* Secondary Transmit Data */
#define RXCSR_V_SECXMT  3
#define RXCSR_M_SECXMT  (1<<RXCSR_V_SECXMT)
    BIT(RCVEN),                             /* Receiver Enable */
#define RXCSR_V_RCVEN   4
#define RXCSR_M_RCVEN   (1<<RXCSR_V_RCVEN)
    BIT(DSCIE),                             /* Data Set Change Interrupt Enable */
#define RXCSR_V_DSCIE   5
#define RXCSR_M_DSCIE   (1<<RXCSR_V_DSCIE)
    BIT(RXIE),                              /* Receive Interrupt Enable */
#define RXCSR_V_RXIE    6
#define RXCSR_M_RXIE    (1<<RXCSR_V_RXIE)
    BIT(RXDONE),                            /* Receive Done */
#define RXCSR_V_RXDONE  7
#define RXCSR_M_RXDONE  (1<<RXCSR_V_RXDONE)
    BIT(STRSYN),                            /* Strip Sync */
#define RXCSR_V_STRSYN  8
#define RXCSR_M_STRSYN  (1<<RXCSR_V_STRSYN)
    BIT(DSR),                               /* Data Set Ready */
#define RXCSR_V_DSR     9
#define RXCSR_M_DSR     (1<<RXCSR_V_DSR)
    BIT(SECRCV),                            /* Secondary Receive Data */
#define RXCSR_V_SECRCV  10
#define RXCSR_M_SECRCV  (1<<RXCSR_V_SECRCV)
    BIT(RXACT),                             /* Receive Active */
#define RXCSR_V_RXACT   11
#define RXCSR_M_RXACT   (1<<RXCSR_V_RXACT)
    BIT(DCD),                               /* Carrier */
#define RXCSR_V_DCD     12
#define RXCSR_M_DCD     (1<<RXCSR_V_DCD)
    BIT(CTS),                               /* Clear to Send */
#define RXCSR_V_CTS     13
#define RXCSR_M_CTS     (1<<RXCSR_V_CTS)
    BIT(RING),                              /* Ring */
#define RXCSR_V_RING    14
#define RXCSR_M_RING    (1<<RXCSR_V_RING)
    BIT(DSCHNG),                            /* Data Set Change */
#define RXCSR_V_DSCHNG  15
#define RXCSR_M_DSCHNG  (1<<RXCSR_V_DSCHNG)
    ENDBITS
};
#define RXCSR_A_MODEM_BITS (RXCSR_M_RING | RXCSR_M_CTS)
#define RXCSR_B_MODEM_BITS (RXCSR_M_DSR | RXCSR_M_DCD)
#define RXCSR_WRITEABLE (RXCSR_M_STRSYN|RXCSR_M_RXIE|RXCSR_M_DSCIE|RXCSR_M_RCVEN|RXCSR_M_SECXMT|RXCSR_M_RTS|RXCSR_M_DTR)

/* RXDBUF - 16XXX2 - receiver Data Buffer register */

static BITFIELD dup_rxdbuf_bits[] = {
    BITF(RXDBUF,8),                         /* Receive Data Buffer */
#define RXDBUF_V_RXDBUF  0
#define RXDBUF_S_RXDBUF  8
#define RXDBUF_M_RXDBUF  (((1<<RXDBUF_S_RXDBUF)-1)<<RXDBUF_V_RXDBUF)
    BIT(RSTRMSG),                           /* Receiver Start of Message */
#define RXDBUF_V_RSTRMSG 8
#define RXDBUF_M_RSTRMSG (1<<RXDBUF_V_RSTRMSG)
    BIT(RENDMSG),                           /* Receiver End Of Message */
#define RXDBUF_V_RENDMSG 9
#define RXDBUF_M_RENDMSG (1<<RXDBUF_V_RENDMSG)
    BIT(RABRT),                             /* Receiver Abort */
#define RXDBUF_V_RABRT   10
#define RXDBUF_M_RABRT   (1<<RXDBUF_V_RABRT)
    BITNCF(1),                              /* reserved */
    BIT(RCRCER),                            /* Receiver CRC Error */
#define RXDBUF_V_RCRCER  12
#define RXDBUF_M_RCRCER  (1<<RXDBUF_V_RCRCER)
    BITNCF(1),                              /* reserved */
    BIT(RXOVR),                             /* Receiver Overrun */
#define RXDBUF_V_RXOVR   14
#define RXDBUF_M_RXOVR   (1<<RXDBUF_V_RXOVR)
    BIT(RXERR),                             /* Receiver Error */
#define RXDBUF_V_RXERR   15
#define RXDBUF_M_RXERR   (1<<RXDBUF_V_RXERR)
    ENDBITS
};
#define RXDBUF_MBZ ((1<<13)|(1<<11))

/* PARCSR - 16XXX2 - Parameter Control/Status register */

static BITFIELD dup_parcsr_bits[] = {
    BITF(ADSYNC,8),                         /* Secondart Station Address/Receiver Sync Char */
#define PARCSR_V_ADSYNC  0
#define PARCSR_S_ADSYNC  8
#define PARCSR_M_ADSYNC  (((1<<PARCSR_S_ADSYNC)-1)<<PARCSR_V_ADSYNC)
    BITNCF(1),                              /* reserved */
    BIT(NOCRC),                             /* No CRC */
#define PARCSR_V_NOCRC   9
#define PARCSR_M_NOCRC   (1<<PARCSR_V_NOCRC)
    BITNCF(2),                              /* reserved */
    BIT(SECMODE),                           /* Secondary Mode Select */
#define PARCSR_V_SECMODE 12
#define PARCSR_M_SECMODE (1<<PARCSR_V_SECMODE)
    BITNCF(2),                              /* reserved */
    BIT(DECMODE),                           /* DEC Mode */
#define PARCSR_V_DECMODE 15
#define PARCSR_M_DECMODE (1<<PARCSR_V_DECMODE)
    ENDBITS
};
#define PARCSR_MBZ ((1<<14)|(1<<13)|(1<<11)|(1<<10)|(1<<8))
#define PARCSR_WRITEABLE (PARCSR_M_DECMODE|PARCSR_M_SECMODE|PARCSR_M_NOCRC|PARCSR_M_ADSYNC)

/* TXCSR - 16XXX4 - Transmitter Control/Status register */

static BITFIELD dup_txcsr_bits[] = {
    BITNCF(3),                              /* reserved */
    BIT(HALFDUP),                           /* Half Duplex */
#define TXCSR_V_HALFDUP  3
#define TXCSR_M_HALFDUP  (1<<TXCSR_V_HALFDUP)
    BIT(SEND),                              /* Enable Transmit */
#define TXCSR_V_SEND     4
#define TXCSR_M_SEND     (1<<TXCSR_V_SEND)
    BITNCF(1),                              /* reserved */
    BIT(TXIE),                              /* Transmit Interrupt Enable */
#define TXCSR_V_TXIE     6
#define TXCSR_M_TXIE     (1<<TXCSR_V_TXIE)
    BIT(TXDONE),                            /* Transmit Done */
#define TXCSR_V_TXDONE   7
#define TXCSR_M_TXDONE   (1<<TXCSR_V_TXDONE)
    BIT(DRESET),                            /* Device Reset */
#define TXCSR_V_DRESET   8
#define TXCSR_M_DRESET   (1<<TXCSR_V_DRESET)
    BIT(TXACT),                             /* Transmit Active */
#define TXCSR_V_TXACT    9
#define TXCSR_M_TXACT    (1<<TXCSR_V_TXACT)
    BIT(MAIDATA),                           /* Maintenance Mode Data Bit */
#define TXCSR_V_MAIDATA  10
#define TXCSR_M_MAIDATA  (1<<TXCSR_V_MAIDATA)
    BITF(MAISEL,2),                         /* Maintenance Select B and A */
#define TXCSR_V_MAISEL   11
#define TXCSR_S_MAISEL   2
#define TXCSR_M_MAISEL   (((1<<TXCSR_S_MAISEL)-1)<<TXCSR_V_MAISEL)
#define TXCSR_GETMAISEL(x) (((x) & TXCSR_M_MAISEL) >> TXCSR_V_MAISEL)
    BIT(MAISSCLK),                          /* Maintenance Single Step Clock */
#define TXCSR_V_MAISSCLK 13
#define TXCSR_M_MAISSCLK (1<<TXCSR_V_MAISSCLK)
    BIT(TXMNTOUT),                          /* Transmit Maint Data Out */
#define TXCSR_V_TXMNTOUT 14
#define TXCSR_M_TXMNTOUT (1<<TXCSR_V_TXMNTOUT)
    BIT(TXDLAT),                            /* Transmit Data Late */
#define TXCSR_V_TXDLAT   15
#define TXCSR_M_TXDLAT   (1<<TXCSR_V_TXDLAT)
    ENDBITS
};
#define TXCSR_MBZ ((1<<5)|(1<<2)|(1<<1)|(1<<0))
#define TXCSR_WRITEABLE (TXCSR_M_MAISSCLK|TXCSR_M_MAISEL|TXCSR_M_MAIDATA|TXCSR_M_DRESET|TXCSR_M_TXIE|TXCSR_M_SEND|TXCSR_M_HALFDUP)

/* TXDBUF - 16XXX6 - transmitter Data Buffer register */

static BITFIELD dup_txdbuf_bits[] = {
    BITF(TXDBUF,8),                         /* Transmit Data Buffer */
#define TXDBUF_V_TXDBUF  0
#define TXDBUF_S_TXDBUF  8
#define TXDBUF_M_TXDBUF  (((1<<TXDBUF_S_TXDBUF)-1)<<TXDBUF_V_TXDBUF)
    BIT(TSOM),                              /* Transmit Start of Message */
#define TXDBUF_V_TSOM    8
#define TXDBUF_M_TSOM    (1<<TXDBUF_V_TSOM)
    BIT(TEOM),                              /* End of Transmitted Message */
#define TXDBUF_V_TEOM    9
#define TXDBUF_M_TEOM    (1<<TXDBUF_V_TEOM)
    BIT(TABRT),                             /* Transmit Abort */
#define TXDBUF_V_TABRT   10
#define TXDBUF_M_TABRT   (1<<TXDBUF_V_TABRT)
    BIT(MAINTT),                            /* Maintenance Timer */
#define TXDBUF_V_MAINTT  11
#define TXDBUF_M_MAINTT  (1<<TXDBUF_V_MAINTT)
    BIT(TCRCTIN),                           /* Transmit CSR Input */
#define TXDBUF_V_TCRCTIN 12
#define TXDBUF_M_TCRCTIN (1<<TXDBUF_V_TCRCTIN)
    BITNCF(1),                              /* reserved */
    BIT(RCRCTIN),                           /* Receive CSR Input */
#define TXDBUF_V_RCRCTIN 14
#define TXDBUF_M_RCRCTIN (1<<TXDBUF_V_RCRCTIN)
    BITNCF(1),                              /* reserved */
    ENDBITS
};
#define TXDBUF_MBZ ((1<<15)|(1<<13))
#define TXDBUF_WRITEABLE (TXDBUF_M_TABRT|TXDBUF_M_TEOM|TXDBUF_M_TSOM|TXDBUF_M_TXDBUF)


/* Equivalent register definitions for DPV11. Some bits are common; some are nearly common
   but with slightly different semantics; some are different altogether */

/* DPV RXCSR - 16XXX0 - receiver control/status register */

static BITFIELD dpv_rxcsr_bits[] = {
    BIT(DPV_SFRL),                           /* Set Freq / Remote Loop */
#define RXCSR_V_DPV_SFRL     0
#define RXCSR_M_DPV_SFRL     (1<<RXCSR_V_DPV_SFRL)
    BIT(DPV_DTR),                               /* Data Terminal Ready */
#define RXCSR_V_DPV_DTR      1
#define RXCSR_M_DPV_DTR      (1<<RXCSR_V_DPV_DTR)
    BIT(DPV_RTS),                               /* Request To Send */
#define RXCSR_V_DPV_RTS      2
#define RXCSR_M_DPV_RTS      (1<<RXCSR_V_DPV_RTS)
    BIT(DPV_DPV_LL),                            /* Local Loop */
#define RXCSR_V_DPV_LL       3
#define RXCSR_M_DPV_LL       (1<<RXCSR_V_DPV_LL)
    BIT(DPV_RCVEN),                             /* Receiver Enable */
#define RXCSR_V_DPV_RCVEN    4
#define RXCSR_M_DPV_RCVEN    (1<<RXCSR_V_DPV_RCVEN)
    BIT(DPV_DSCIE),                             /* Data Set Change Interrupt Enable */
#define RXCSR_V_DPV_DSCIE    5
#define RXCSR_M_DPV_DSCIE    (1<<RXCSR_V_DPV_DSCIE)
    BIT(DPV_RXIE),                              /* Receive Interrupt Enable */
#define RXCSR_V_DPV_RXIE     6
#define RXCSR_M_DPV_RXIE     (1<<RXCSR_V_DPV_RXIE)
    BIT(DPV_RXDONE),                            /* Receive Done */
#define RXCSR_V_DPV_RXDONE   7
#define RXCSR_M_DPV_RXDONE   (1<<RXCSR_V_DPV_RXDONE)
    BIT(DPV_DETSYN),                            /* Sync Detected */
#define RXCSR_V_DPV_DETSYN   8
#define RXCSR_M_DPV_DETSYN   (1<<RXCSR_V_DPV_DETSYN)
    BIT(DPV_DSR),                               /* Data Set Ready */
#define RXCSR_V_DPV_DSR      9
#define RXCSR_M_DPV_DSR      (1<<RXCSR_V_DPV_DSR)
    BIT(DPV_RSTARY),                            /* Receiver Status Ready */
#define RXCSR_V_DPV_RSTARY   10
#define RXCSR_M_DPV_RSTARY   (1<<RXCSR_V_DPV_RSTARY)
    BIT(DPV_RXACT),                             /* Receive Active */
#define RXCSR_V_DPV_RXACT    11
#define RXCSR_M_DPV_RXACT    (1<<RXCSR_V_DPV_RXACT)
    BIT(DPV_DCD),                               /* Carrier */
#define RXCSR_V_DPV_DCD      12
#define RXCSR_M_DPV_DCD      (1<<RXCSR_V_DPV_DCD)
    BIT(DPV_CTS),                               /* Clear to Send */
#define RXCSR_V_DPV_CTS      13
#define RXCSR_M_DPV_CTS      (1<<RXCSR_V_DPV_CTS)
    BIT(DPV_RING),                              /* Ring */
#define RXCSR_V_DPV_RING     14
#define RXCSR_M_DPV_RING     (1<<RXCSR_V_DPV_RING)
    BIT(DPV_DSCHNG),                            /* Data Set Change */
#define RXCSR_V_DPV_DSCHNG   15
#define RXCSR_M_DPV_DSCHNG   (1<<RXCSR_V_DPV_DSCHNG)
    ENDBITS
};
#define RXCSR_DPV_MODEM_BITS (RXCSR_M_DPV_RING|RXCSR_M_DPV_CTS|RXCSR_M_DPV_DSR|RXCSR_M_DPV_DCD)
#define RXCSR_DPV_WRITEABLE (RXCSR_M_DPV_SFRL|RXCSR_M_DPV_DTR|RXCSR_M_DPV_RTS|RXCSR_M_DPV_LL|RXCSR_M_DPV_RCVEN|RXCSR_M_DPV_DSCIE|RXCSR_M_DPV_RXIE)

/* DPV RXDBUF - 16XXX2 - receiver Data Buffer register */

static BITFIELD dpv_rxdbuf_bits[] = {
    BITF(DPV_RXDBUF,8),                         /* Receive Data Buffer */
#define RXDBUF_V_DPV_RXDBUF  0
#define RXDBUF_S_DPV_RXDBUF  8
#define RXDBUF_M_DPV_RXDBUF  (((1<<RXDBUF_S_DPV_RXDBUF)-1)<<RXDBUF_V_DPV_RXDBUF)
    BIT(DPV_RSTRMSG),                           /* Receiver Start of Message */
#define RXDBUF_V_DPV_RSTRMSG 8
#define RXDBUF_M_DPV_RSTRMSG (1<<RXDBUF_V_DPV_RSTRMSG)
    BIT(DPV_RENDMSG),                           /* Receiver End Of Message */
#define RXDBUF_V_DPV_RENDMSG 9
#define RXDBUF_M_DPV_RENDMSG (1<<RXDBUF_V_DPV_RENDMSG)
    BIT(DPV_RABRT),                             /* Receiver Abort */
#define RXDBUF_V_DPV_RABRT   10
#define RXDBUF_M_DPV_RABRT   (1<<RXDBUF_V_DPV_RABRT)
    BIT(DPV_RXOVR),                             /* Receiver Overrun */
#define RXDBUF_V_DPV_RXOVR   11
#define RXDBUF_M_DPV_RXOVR   (1<<RXDBUF_V_DPV_RXOVR)
    BITF(DPV_ABC,3),                           /* Assembled Bit Count */
#define RXDBUF_V_DPV_ABC     12
#define RXDBUF_S_DPV_ABC     3
#define RXDBUF_M_DPV_ABC     (((1<<RXDBUF_S_DPV_ABC)-1)<<RXDBUF_V_DPV_ABC)
    BIT(DPV_RCRCER),                            /* Receiver CRC Error */
#define RXDBUF_V_DPV_RCRCER  15
#define RXDBUF_M_DPV_RCRCER  (1<<RXDBUF_V_DPV_RCRCER)
    ENDBITS
};

/* DPV PCSAR - 16XXX2 - Parameter Control/Status register */

static BITFIELD dpv_parcsr_bits[] = {
    BITF(DPV_ADSYNC,8),                         /* Secondary Station Address/Receiver Sync Char */
#define PARCSR_V_DPV_ADSYNC   0
#define PARCSR_S_DPV_ADSYNC   8
#define PARCSR_M_DPV_ADSYNC   (((1<<PARCSR_S_DPV_ADSYNC)-1)<<PARCSR_V_DPV_ADSYNC)
    BITF(DPV_ERRDET,3),                         /* CRC Type */
#define PARCSR_V_DPV_ERRDET   8
#define PARCSR_S_DPV_ERRDET   3
#define PARCSR_M_DPV_ERRDET   (((1<<PARCSR_S_DPV_ERRDET)-1)<<PARCSR_V_DPV_ERRDET)
    BIT(DPV_IDLEMODE),                          /* Idle Mode Select */
#define PARCSR_V_DPV_IDLEMODE 11
#define PARCSR_M_DPV_IDLEMODE (1<<PARCSR_V_DPV_IDLEMODE)
    BIT(DPV_SECMODE),                           /* Secondary Mode Select */
#define PARCSR_V_DPV_SECMODE  12
#define PARCSR_M_DPV_SECMODE  (1<<PARCSR_V_DPV_SECMODE)
    BIT(DPV_STRSYN),                            /* Strip Sync */
#define PARCSR_V_DPV_STRSYN   13
#define PARCSR_M_DPV_STRSYN   (1<<PARCSR_V_DPV_STRSYN)
    BIT(DPV_PROTSEL),                           /* Protocol Select */
#define PARCSR_V_DPV_PROTSEL  14
#define PARCSR_M_DPV_PROTSEL  (1<<PARCSR_V_DPV_PROTSEL)
    BIT(DPV_APA),                               /* All Parties Address Mode */
#define PARCSR_V_DPV_APA      15
#define PARCSR_M_DPV_APA      (1<<PARCSR_V_DPV_APA)
    ENDBITS
};

/* DPV PCSCR - 16XXX4 - Parameter Control / Character Length register */

static BITFIELD dpv_txcsr_bits[] = {
    BIT(DPV_RESET),                           /* Device Reset */
#define TXCSR_V_DPV_RESET        0
#define TXCSR_M_DPV_RESET        (1<<TXCSR_V_DPV_RESET)
    BIT(DPV_TXACT),                           /* Transmitter Active */
#define TXCSR_V_DPV_TXACT        1
#define TXCSR_M_DPV_TXACT        (1<<TXCSR_V_DPV_TXACT)
    BIT(DPV_TBEMPTY),                         /* Transmit Buffer Empty (DONE) */
#define TXCSR_V_DPV_TBEMPTY      2
#define TXCSR_M_DPV_TBEMPTY      (1<<TXCSR_V_DPV_TBEMPTY)
    BIT(DPV_MAINT),                           /* Maintenance Mode Select */
#define TXCSR_V_DPV_MAINT        3
#define TXCSR_M_DPV_MAINT        (1<<TXCSR_V_DPV_MAINT)
    BIT(DPV_SEND),                            /* Enable Transmit */
#define TXCSR_V_DPV_SEND         4
#define TXCSR_M_DPV_SEND         (1<<TXCSR_V_DPV_SEND)
    BIT(DPV_SQTM),                            /* SQ/TM */
#define TXCSR_V_DPV_SQTM         5
#define TXCSR_M_DPV_SQTM         (1<<TXCSR_V_DPV_SQTM)
    BIT(DPV_TXIE),                            /* Transmit Interrupt Enable */
#define TXCSR_V_DPV_TXIE         6
#define TXCSR_M_DPV_TXIE         (1<<TXCSR_V_DPV_TXIE)
    BITNCF(1),                               /* reserved */
    BITF(DPV_RXCHARSIZE,3),                  /* Receive Character Size*/
#define TXCSR_V_DPV_RXCHARSIZE    8
#define TXCSR_S_DPV_RXCHARSIZE    3
#define TXCSR_M_DPV_RXCHARSIZE    (((1<<TXCSR_S_DPV_RXCHARSIZE)-1)<<TXCSR_V_DPV_RXCHARSIZE)
    BIT(DPV_EXTCONT),                        /* Extended Control Field */
#define TXCSR_V_DPV_EXTCONT       11
#define TXCSR_M_DPV_EXTCONT       (1<<TXCSR_V_DPV_EXTCONT)
    BIT(DPV_EXTADDR),                        /* Extended Control Field */
#define TXCSR_V_DPV_EXTADDR       12
#define TXCSR_M_DPV_EXTADDR       (1<<TXCSR_V_DPV_EXTADDR)
    BITF(DPV_TXCHARSIZE,3),                  /* Transmit Character Size*/
#define TXCSR_V_DPV_TXCHARSIZE    13
#define TXCSR_S_DPV_TXCHARSIZE    3
#define TXCSR_M_DPV_TXCHARSIZE    (((1<<TXCSR_S_DPV_TXCHARSIZE)-1)<<TXCSR_V_DPV_TXCHARSIZE)
    ENDBITS
};
#define TXCSR_DPV_MBZ ((1<<7))
#define TXCSR_DPV_WRITEABLE (TXCSR_M_DPV_RESET|TXCSR_M_DPV_MAINT|TXCSR_M_DPV_SEND|TXCSR_M_DPV_SQTM|TXCSR_M_DPV_TXIE|TXCSR_M_DPV_RXCHARSIZE|TXCSR_M_DPV_EXTCONT|TXCSR_M_DPV_EXTADDR|TXCSR_M_DPV_TXCHARSIZE)

/* DPV TDSR - 16XXX6 - Transmitter Data and Status  register */

static BITFIELD dpv_txdbuf_bits[] = {
    BITF(DPV_TXDBUF,8),                         /* Transmit Data Buffer */
#define TXDBUF_V_DPV_TXDBUF  0
#define TXDBUF_S_DPV_TXDBUF  8
#define TXDBUF_M_DPV_TXDBUF  (((1<<TXDBUF_S_DPV_TXDBUF)-1)<<TXDBUF_V_DPV_TXDBUF)
    BIT(DPV_TSOM),                              /* Transmit Start of Message */
#define TXDBUF_V_DPV_TSOM    8
#define TXDBUF_M_DPV_TSOM    (1<<TXDBUF_V_DPV_TSOM)
    BIT(DPV_TEOM),                              /* End of Transmitted Message */
#define TXDBUF_V_DPV_TEOM    9
#define TXDBUF_M_DPV_TEOM    (1<<TXDBUF_V_DPV_TEOM)
    BIT(DPV_TABRT),                             /* Transmit Abort */
#define TXDBUF_V_DPV_TABRT   10
#define TXDBUF_M_DPV_TABRT   (1<<TXDBUF_V_DPV_TABRT)
    BIT(DPV_GOAHEAD),                           /* Use Go Ahead */
#define TXDBUF_V_DPV_GOAHEAD 11
#define TXDBUF_M_DPV_GOAHEAD (1<<TXDBUF_V_DPV_GOAHEAD)
    BITNCF(3),                                  /* reserved */
    BIT(DPV_TERR),                              /* Transmit Error */
#define TXDBUF_V_DPV_TERR    15
#define TXDBUF_M_DPV_TERR    (1<<TXDBUF_V_DPV_TERR)
    ENDBITS
};
#define TXDBUF_DPV_MBZ ((7<<12))
#define TXDBUF_DPV_WRITEABLE (TXDBUF_M_DPV_GOAHEAD|TXDBUF_M_DPV_TABRT|TXDBUF_M_DPV_TEOM|TXDBUF_M_DPV_TSOM|TXDBUF_M_DPV_TXDBUF)


#define TRAILING_SYNS 8
const uint8 tsyns[TRAILING_SYNS] = {0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96}; 

/* DUP data structures

   dup_dev      DUP device descriptor
   dup_unit     DUP unit descriptor
   dup_reg      DUP register list
*/

#define IOLN_DUP        010

DIB dup_dib = {
    IOBA_AUTO,
    IOLN_DUP * INITIAL_DUP_LINES,
    &dup_rd,    /* read */
    &dup_wr,    /* write */
    2,          /* # of vectors */
    IVCL (DUPRX),
    VEC_AUTO,
    { &dup_rxinta, &dup_txinta },/* int. ack. routines */
    IOLN_DUP,    /* IO space per line */
};

static UNIT dup_unit_template = {
    UDATA (&dup_svc, UNIT_ATTABLE|UNIT_IDLE, 0), 
    };

static UNIT dup_poll_unit_template = {
    UDATA (&dup_poll_svc, UNIT_DIS|UNIT_IDLE, 0), 
    };

static UNIT dup_units[DUP_LINES+1];    /* One unit per line and a polling unit */

static REG dup_reg[] = {
    { BRDATADF (RXCSR,          dup_rxcsr,  DEV_RDX, 16, DUP_LINES, "receive control/status register",  dup_rxcsr_bits) },
    { BRDATADF (RXDBUF,        dup_rxdbuf,  DEV_RDX, 16, DUP_LINES, "receive data buffer",              dup_rxdbuf_bits) },
    { BRDATADF (PARCSR,        dup_parcsr,  DEV_RDX, 16, DUP_LINES, "receive control/status register",  dup_parcsr_bits) },
    { BRDATADF (TXCSR,          dup_txcsr,  DEV_RDX, 16, DUP_LINES, "transmit control/status register", dup_txcsr_bits) },
    { BRDATADF (TXDBUF,        dup_txdbuf,  DEV_RDX, 16, DUP_LINES, "transmit data buffer",             dup_txdbuf_bits) },
    { BRDATAD  (W3,                dup_W3,  DEV_RDX,  1, DUP_LINES, "Clear Option Enable") },
    { BRDATAD  (W5,                dup_W5,  DEV_RDX,  1, DUP_LINES, "A Dataset Control Enable") },
    { BRDATAD  (W6,                dup_W6,  DEV_RDX,  1, DUP_LINES, "A and B Dataset Control Enable") },
    { GRDATAD  (RXINT,            dup_rxi,  DEV_RDX, DUP_LINES,  0, "receive interrupts") },
    { GRDATAD  (TXINT,            dup_txi,  DEV_RDX, DUP_LINES,  0, "transmit interrupts") },
    { BRDATAD  (WAIT,            dup_wait,       10, 32, DUP_LINES, "delay time for transmit/receive bytes"), PV_RSPC },
    { BRDATAD  (SPEED,          dup_speed,       10, 32, DUP_LINES, "line bit rate"), PV_RCOMMA },
    { BRDATAD  (TPOFFSET, dup_xmtpkoffset,  DEV_RDX, 16, DUP_LINES, "transmit assembly packet offset") },
    { BRDATAD  (TPSIZE,    dup_xmtpkbytes,  DEV_RDX, 16, DUP_LINES, "transmit digest packet size") },
    { BRDATAD  (TPDELAY,dup_xmtpkdelaying,  DEV_RDX, 16, DUP_LINES, "transmit packet completion delay") },
    { BRDATAD  (TPSTART,   dup_xmtpkstart,  DEV_RDX, 32, DUP_LINES, "transmit digest packet start time") },
    { BRDATAD  (RPINOFF,   dup_rcvpkinoff,  DEV_RDX, 16, DUP_LINES, "receive digest packet offset") },
    { BRDATAD  (CORRUPT,   dup_corruption,  DEV_RDX, 32, DUP_LINES, "data corruption factor (0.1%)") },
    { NULL }
    };

/* We need a DPV version of the REG structure because the CSR definitions and settable jumpers are different */

static REG dpv_reg[] = {
    { BRDATADF (RXCSR,          dup_rxcsr,  DEV_RDX, 16, DUP_LINES, "receive control/status register",  dpv_rxcsr_bits) },
    { BRDATADF (RXDBUF,        dup_rxdbuf,  DEV_RDX, 16, DUP_LINES, "receive data buffer",              dpv_rxdbuf_bits) },
    { BRDATADF (PARCSR,        dup_parcsr,  DEV_RDX, 16, DUP_LINES, "receive control/status register",  dpv_parcsr_bits) },
    { BRDATADF (TXCSR,          dup_txcsr,  DEV_RDX, 16, DUP_LINES, "transmit control/status register", dpv_txcsr_bits) },
    { BRDATADF (TXDBUF,        dup_txdbuf,  DEV_RDX, 16, DUP_LINES, "transmit data buffer",             dpv_txdbuf_bits) },
    { GRDATAD  (RXINT,            dup_rxi,  DEV_RDX, DUP_LINES,  0, "receive interrupts") },
    { GRDATAD  (TXINT,            dup_txi,  DEV_RDX, DUP_LINES,  0, "transmit interrupts") },
    { BRDATAD  (WAIT,            dup_wait,       10, 32, DUP_LINES, "delay time for transmit/receive bytes"), PV_RSPC },
    { BRDATAD  (SPEED,          dup_speed,       10, 32, DUP_LINES, "line bit rate"), PV_RCOMMA },
    { BRDATAD  (TPOFFSET, dup_xmtpkoffset,  DEV_RDX, 16, DUP_LINES, "transmit assembly packet offset") },
    { BRDATAD  (TPSIZE,    dup_xmtpkbytes,  DEV_RDX, 16, DUP_LINES, "transmit digest packet size") },
    { BRDATAD  (TPDELAY,dup_xmtpkdelaying,  DEV_RDX, 16, DUP_LINES, "transmit packet completion delay") },
    { BRDATAD  (TPSTART,   dup_xmtpkstart,  DEV_RDX, 32, DUP_LINES, "transmit digest packet start time") },
    { BRDATAD  (RPINOFF,   dup_rcvpkinoff,  DEV_RDX, 16, DUP_LINES, "receive digest packet offset") },
    { BRDATAD  (CORRUPT,   dup_corruption,  DEV_RDX, 32, DUP_LINES, "data corruption factor (0.1%)") },
    { NULL }
    };

static TMLN *dup_ldsc = NULL;                                  /* line descriptors */
static TMXR dup_desc = { INITIAL_DUP_LINES, 0, 0, NULL };      /* mux descriptor */

static MTAB dup_mod[] = {
    { MTAB_XTD|MTAB_VUN,          0, "SPEED", "SPEED=bits/sec (0=unrestricted)" ,
        &dup_setspeed, &dup_showspeed, NULL, "Display rate limit" },
    { MTAB_XTD|MTAB_VUN,          0, "CORRUPTION", "CORRUPTION=factor (0=uncorrupted)" ,
        &dup_setcorrupt, &dup_showcorrupt, NULL, "Display corruption factor (0.1% of packets)" },
    { MTAB_XTD|MTAB_VUN,          1, "W3", NULL ,
        NULL, &dup_show_W3, NULL, "Display Reset Option" },
    { MTAB_XTD|MTAB_VUN,          1, NULL, "W3" ,
        &dup_set_W3, NULL,         NULL, "Enable Reset Option" },
    { MTAB_XTD|MTAB_VUN,          0, NULL, "NOW3" ,
        &dup_set_W3, NULL,         NULL, "Disable Reset Option" },
    { MTAB_XTD|MTAB_VUN,          1, "W5", NULL ,
        NULL, &dup_show_W5, NULL, "Display A Dataset Control Option" },
    { MTAB_XTD|MTAB_VUN,          1, NULL, "W5" ,
        &dup_set_W5, NULL,         NULL, "Enable A Dataset Control Option" },
    { MTAB_XTD|MTAB_VUN,          0, NULL, "NOW5" ,
        &dup_set_W5, NULL,         NULL, "Disable A Dataset Control Option" },
    { MTAB_XTD|MTAB_VUN,          1, "W6", NULL ,
        NULL, &dup_show_W6, NULL, "Display A & B Dataset Control Option" },
    { MTAB_XTD|MTAB_VUN,          1, NULL, "W6" ,
        &dup_set_W6, NULL,         NULL, "Enable A & B Dataset Control Option" },
    { MTAB_XTD|MTAB_VUN,          0, NULL, "NOW6" ,
        &dup_set_W6, NULL,         NULL, "Disable A & B Dataset Control  Option" },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 020, "ADDRESS", "ADDRESS",
        &set_addr, &show_addr, NULL, "Bus address" },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 1, "VECTOR", "VECTOR",
        &set_vec, &show_vec_mux, (void *) &dup_desc, "Interrupt vector" },
    { MTAB_XTD|MTAB_VDV|MTAB_NMO, 1, "CONNECTIONS", NULL,
        NULL, &tmxr_show_cstat, (void *) &dup_desc, "Display current connections" },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "LINES", "LINES=n",
        &dup_setnl, &tmxr_show_lines, (void *) &dup_desc, "Display number of lines" },
    { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "SYNC", NULL,
      NULL, &tmxr_show_sync, NULL, "Display attachable DDCMP synchronous links" },
    { 0 }
    };

static MTAB dpv_mod[] = {
    { MTAB_XTD|MTAB_VUN,          0, "SPEED", "SPEED=bits/sec (0=unrestricted)" ,
        &dup_setspeed, &dup_showspeed, NULL, "Display rate limit" },
    { MTAB_XTD|MTAB_VUN,          0, "CORRUPTION", "CORRUPTION=factor (0=uncorrupted)" ,
        &dup_setcorrupt, &dup_showcorrupt, NULL, "Display corruption factor (0.1% of packets)" },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 020, "ADDRESS", "ADDRESS",
        &set_addr, &show_addr, NULL, "Bus address" },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 1, "VECTOR", "VECTOR",
        &set_vec, &show_vec_mux, (void *) &dup_desc, "Interrupt vector" },
    { MTAB_XTD|MTAB_VDV|MTAB_NMO, 1, "CONNECTIONS", NULL,
        NULL, &tmxr_show_cstat, (void *) &dup_desc, "Display current connections" },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "LINES", "LINES=n",
        &dup_setnl, &tmxr_show_lines, (void *) &dup_desc, "Display number of lines" },
    { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "SYNC", NULL,
      NULL, &tmxr_show_sync, NULL, "Display attachable DDCMP synchronous links" },
    { 0 }
    };

/* debugging bitmaps */
#define DBG_REG  0x0001                                 /* trace read/write registers */
#define DBG_INT  0x0002                                 /* display transfer requests */
#define DBG_PKT  (TMXR_DBG_PXMT|TMXR_DBG_PRCV)          /* display packets */
#define DBG_XMT  TMXR_DBG_XMT                           /* display Transmitted Data */
#define DBG_RCV  TMXR_DBG_RCV                           /* display Received Data */
#define DBG_MDM  TMXR_DBG_MDM                           /* display Modem SignalTransitions */
#define DBG_CON  TMXR_DBG_CON                           /* display connection activities */
#define DBG_TRC  TMXR_DBG_TRC                           /* display trace routine calls */
#define DBG_ASY  TMXR_DBG_ASY                           /* display Asynchronous Activities */

static DEBTAB dup_debug[] = {
  {"REG",    DBG_REG},
  {"INT",    DBG_INT},
  {"PKT",    DBG_PKT},
  {"XMT",    DBG_XMT},
  {"RCV",    DBG_RCV},
  {"MDM",    DBG_MDM},
  {"CON",    DBG_CON},
  {"TRC",    DBG_TRC},
  {"ASY",    DBG_ASY},
  {0}
};

/*
   We have two devices defined here (dup_dev and dpv_dev) which have the 
   same units.  This would normally never be allowed since two devices can't
   actually share units.  This problem is avoided in this case since both 
   devices start out as disabled and the logic in dup_reset allows only 
   one of these devices to be enabled at a time.  The DUP device is allowed 
   on Unibus systems and the DPV device Qbus systems.
   This monkey business is necessary due to the fact that although both
   the DUP and DPV devices have almost the same functionality and almost
   the same register programming interface, they are different enough that
   they fall in different priorities in the autoconfigure address and vector
   rules.
   This 'shared' unit model therefore means we can't call the 
   find_dev_from_unit api to uniquely determine the device structure.  
   We define the DUPDPTR macro to return the active device pointer when 
   necessary.

   The general approach for supporting the two device types is to re-use as much
   code as possible for the DUP when acting as a DPV, including using the "wrong"
   names for CSR bits if the bits are equivalent. So the device definitions below are
   only different where actually required. As with the DUP, currently only DDCMP
   is supported, and some register bits that are for BOP only are not implemented.
 */
DEVICE dup_dev = {
    "DUP", dup_units, dup_reg, dup_mod,
    2, 10, 31, 1, DEV_RDX, 8,
    NULL, NULL, &dup_reset,
    NULL, &dup_attach, &dup_detach,
    &dup_dib, DEV_DIS | DEV_DISABLE | DEV_UBUS | DEV_DEBUG | DEV_DONTAUTO, 0,
    dup_debug, NULL, NULL, &dup_help, dup_help_attach, &dup_desc, 
    &dup_description
    };

DEVICE dpv_dev = {
    "DPV", dup_units, dpv_reg, dpv_mod,
    2, 10, 31, 1, DEV_RDX, 8,
    NULL, NULL, &dup_reset,
    NULL, &dup_attach, &dup_detach,
    &dup_dib, DEV_DIS | DEV_DISABLE | DEV_QBUS | DEV_DEBUG | DEV_DONTAUTO, 0,
    dup_debug, NULL, NULL, &dup_help, dup_help_attach, &dup_desc, 
    &dup_description
    };

#define DUPDPTR ((UNIBUS) ? &dup_dev : &dpv_dev)

/* Register names for Debug tracing */
static const char *dup_rd_regs[] =
    {"RXCSR ", "RXDBUF", "TXCSR ", "TXDBUF" };
static const char *dup_wr_regs[] = 
    {"RXCSR ", "PARCSR", "TXCSR ", "TXDBUF"};


/* DUP11/DPV11 bit synchronous interface routines

   dup_rd       I/O page read
   dup_wr       I/O page write
   dup_svc      process event
   dup_poll_svc process polling events
   dup_reset    process reset
   dup_setnl    set number of lines
   dup_attach   process attach
   dup_detach   process detach
*/

static t_stat dup_rd (int32 *data, int32 PA, int32 access)
{
static BITFIELD* bitdefs[] = {dup_rxcsr_bits, dup_rxdbuf_bits, dup_txcsr_bits, dup_txdbuf_bits};
static BITFIELD* dpv_bitdefs[] = {dpv_rxcsr_bits, dpv_rxdbuf_bits, dpv_txcsr_bits, dpv_txdbuf_bits};
static uint16 *regs[] = {dup_rxcsr, dup_rxdbuf, dup_txcsr, dup_txdbuf};
int32 dup = ((PA - dup_dib.ba) >> 3);                   /* get line num */
int32 orig_val;

if (dup >= dup_desc.lines)                              /* validate line number */
    return SCPE_IERR;

orig_val = regs[(PA >> 1) & 03][dup];
switch ((PA >> 1) & 03) {                               /* case on PA<2:1> */

    case 00:                                            /* RXCSR */
        dup_get_modem (dup);
        *data = dup_rxcsr[dup];
        if (UNIBUS)
            dup_rxcsr[dup] &= ~(RXCSR_M_DSCHNG|RXCSR_M_BDATSET);
        else
            dup_rxcsr[dup] &= ~(RXCSR_M_DPV_DSCHNG);
        break;

    case 01:                                            /* RXDBUF */
        *data = dup_rxdbuf[dup];
        dup_rxcsr[dup] &= ~RXCSR_M_RXDONE;
        if (!UNIBUS)
            dup_rxcsr[dup] &= ~RXCSR_M_DPV_RSTARY;
        if (dup_rxcsr[dup] & RXCSR_M_RXACT)
            sim_activate (dup_units+dup, dup_wait[dup]);
        break;

    case 02:                                            /* TXCSR */
        *data = dup_txcsr[dup];
        break;

    case 03:                                            /* TXDBUF */
        *data = dup_txdbuf[dup];
        break;
    }

sim_debug(DBG_REG, DUPDPTR, "dup_rd(PA=0x%08X [%s], data=0x%X) ", PA, dup_rd_regs[(PA >> 1) & 03], *data);
sim_debug_bits(DBG_REG, DUPDPTR, ((UNIBUS) ? bitdefs[(PA >> 1) & 03] : dpv_bitdefs[(PA >> 1) & 03]),
               (uint32)(orig_val), (uint32)(regs[(PA >> 1) & 03][dup]), TRUE);

return SCPE_OK;
}
static t_stat dup_wr (int32 data, int32 PA, int32 access)
{
static BITFIELD* bitdefs[] = {dup_rxcsr_bits, dup_parcsr_bits, dup_txcsr_bits, dup_txdbuf_bits};
static BITFIELD* dpv_bitdefs[] = {dpv_rxcsr_bits, dpv_parcsr_bits, dpv_txcsr_bits, dpv_txdbuf_bits};
static uint16 *regs[] = {dup_rxcsr, dup_parcsr, dup_txcsr, dup_txdbuf};
int32 dup = ((PA - dup_dib.ba) >> 3);                   /* get line num */
int32 orig_val;

if (dup >= dup_desc.lines)                              /* validate line number */
    return SCPE_IERR;

orig_val = regs[(PA >> 1) & 03][dup];

if (PA & 1)
    data = ((data << 8) | (orig_val & 0xFF)) & 0xFFFF;  /* Merge with original word */
else
    if (access == WRITEB)                               /* byte access? */
        data = (orig_val & 0xFF00) | (data & 0xFF);     /* Merge with original high word */

switch ((PA >> 1) & 03) {                               /* case on PA<2:1> */

    case 00:                                            /* RXCSR */
        dup_set_modem (dup, data);
        if (UNIBUS) {
            dup_rxcsr[dup] &= ~RXCSR_WRITEABLE;
            dup_rxcsr[dup] |= (data & RXCSR_WRITEABLE);
            if ((dup_rxcsr[dup] & RXCSR_M_DTR) &&           /* Upward transition of DTR */
                (!(orig_val & RXCSR_M_DTR)))                /* Enables Receive on the line */
                dup_desc.ldsc[dup].rcve = TRUE;
            if ((dup_rxcsr[dup] & RXCSR_M_RTS) &&           /* Upward transition of RTS */
                (!(orig_val & RXCSR_M_RTS)) &&              /* while receiver is enabled and */
                (dup_rxcsr[dup] & RXCSR_M_RCVEN) &&         /* not stripping sync characters */
                (!(dup_rxcsr[dup] & RXCSR_M_STRSYN)) ) {    /* Receive a SYNC character */
                dup_rxcsr[dup] |= RXCSR_M_RXDONE;
                dup_rxdbuf[dup] &= ~RXDBUF_M_RXDBUF;
                dup_rxdbuf[dup] |= (dup_parcsr[dup] & PARCSR_M_ADSYNC);
                if (dup_rxcsr[dup] & RXCSR_M_RXIE)
                    dup_set_rxint (dup);
            }
        }
        else {
            dup_rxcsr[dup] &= ~RXCSR_DPV_WRITEABLE;
            dup_rxcsr[dup] |= (data & RXCSR_DPV_WRITEABLE);
            if ((dup_rxcsr[dup] & RXCSR_M_DTR) &&           /* Upward transition of DTR */
                (!(orig_val & RXCSR_M_DTR)))                /* Enables Receive on the line */
                dup_desc.ldsc[dup].rcve = TRUE;
        }
        if ((dup_rxcsr[dup] & RXCSR_M_RCVEN) && 
            (!(orig_val & RXCSR_M_RCVEN))) {            /* Upward transition of receiver enable */
            dup_rcv_byte (dup);                         /* start any pending receive */
            }
        if ((!(dup_rxcsr[dup] & RXCSR_M_RCVEN)) && 
            (orig_val & RXCSR_M_RCVEN)) {               /* Downward transition of receiver enable */
            dup_rxdbuf[dup] &= ~RXDBUF_M_RXDBUF;
            dup_rxcsr[dup] &= ~(RXCSR_M_RXACT|RXCSR_M_RXDONE);    /* also clear RXDONE per DUP11 spec. */
            if ((dup_rcvpkinoff[dup] != 0) || 
                (dup_rcvpkbytes[dup] != 0))
                dup_rcvpkinoff[dup] = dup_rcvpkbytes[dup] = 0;
            }
        if ((!(dup_rxcsr[dup] & RXCSR_M_RXIE)) && 
            (orig_val & RXCSR_M_RXIE))                  /* Downward transition of receiver interrupt enable */
            dup_clr_rxint (dup);
        if ((dup_rxcsr[dup] & RXCSR_M_RXIE) && (dup_rxcsr[dup] & RXCSR_M_RXDONE))
            dup_set_rxint (dup);
        break;

    case 01:                                            /* PARCSR */
        if (UNIBUS) {
            dup_parcsr[dup] &= ~PARCSR_WRITEABLE;
            dup_parcsr[dup] |= (data & PARCSR_WRITEABLE);
        } else 
            dup_parcsr[dup] = data ;
        break;

    case 02:                                            /* TXCSR */
        if (UNIBUS) {
            dup_txcsr[dup] &= ~TXCSR_WRITEABLE;
            dup_txcsr[dup] |= (data & TXCSR_WRITEABLE);
            if (dup_txcsr[dup] & TXCSR_M_DRESET) {
                dup_clear(dup, dup_W3[dup]);
                /* must also clear loopback if it was set */
                tmxr_set_line_loopback (&dup_desc.ldsc[dup], FALSE);
                break;
            }
            if (TXCSR_GETMAISEL(dup_txcsr[dup]) != TXCSR_GETMAISEL(orig_val)) { /* Maint Select Changed */
                switch (TXCSR_GETMAISEL(dup_txcsr[dup])) {
                case 0:  /* User/Normal Mode */
                    tmxr_set_line_loopback (&dup_desc.ldsc[dup], FALSE);
                    break;
                case 1:  /* External Loopback Mode */
                case 2:  /* Internal Loopback Mode */
                    tmxr_set_line_loopback (&dup_desc.ldsc[dup], TRUE);
                    break;
                case 3:  /* System Test Mode */
                    break;
                }
            }
            if ((dup_txcsr[dup] & TXCSR_M_TXACT) &&  
                (!(orig_val & TXCSR_M_TXACT))    && 
                (orig_val & TXCSR_M_TXDONE)) {
                dup_txcsr[dup] &= ~TXCSR_M_TXDONE;
            }
            if ((!(dup_txcsr[dup] & TXCSR_M_SEND)) && 
                (orig_val & TXCSR_M_SEND)) {
                dup_txcsr[dup] &= ~TXCSR_M_TXACT;
                dup_put_msg_bytes (dup, NULL, 0, FALSE, TRUE);
            }
            if ((dup_txcsr[dup] & TXCSR_M_HALFDUP) ^ (orig_val & TXCSR_M_HALFDUP))
                tmxr_set_line_halfduplex (dup_desc.ldsc+dup, dup_txcsr[dup] & TXCSR_M_HALFDUP);
            if ((dup_txcsr[dup] & TXCSR_M_TXIE) && 
                (!(orig_val & TXCSR_M_TXIE))    && 
                (dup_txcsr[dup] & TXCSR_M_TXDONE)) {
                dup_set_txint (dup);
            }
        } else {
            dup_txcsr[dup] &= ~TXCSR_DPV_WRITEABLE;
            dup_txcsr[dup] |= (data & TXCSR_DPV_WRITEABLE);
            if (dup_txcsr[dup] & TXCSR_M_DPV_RESET) {
                dup_clear(dup, TRUE); 
                /* must also clear loopback if it was set */
                tmxr_set_line_loopback (&dup_desc.ldsc[dup], FALSE);
                break;
            }
            if ((dup_txcsr[dup] & TXCSR_M_DPV_MAINT) ^ (orig_val & TXCSR_M_DPV_MAINT))   /* maint mode change */
                tmxr_set_line_loopback (&dup_desc.ldsc[dup], dup_txcsr[dup] & TXCSR_M_DPV_MAINT );
            if ((!(dup_txcsr[dup] & TXCSR_M_DPV_SEND)) && 
                (orig_val & TXCSR_M_DPV_SEND)) {
                dup_txcsr[dup] &= ~TXCSR_M_DPV_TXACT;
                dup_put_msg_bytes (dup, NULL, 0, FALSE, TRUE);
            }
            if ((dup_txcsr[dup] & TXCSR_M_DPV_TXIE) && 
                (!(orig_val & TXCSR_M_DPV_TXIE))    && 
                (dup_txcsr[dup] & TXCSR_M_DPV_TBEMPTY)) {
                dup_set_txint (dup);
            }
            /* Receive character length, transmit character length, extended HDLC fields, SQ/TM  not supported */
        }
        break;

    case 03:                                            /* TXDBUF */
        if (UNIBUS) {
            dup_txdbuf[dup] &= ~TXDBUF_WRITEABLE;
            dup_txdbuf[dup] |= (data & TXDBUF_WRITEABLE);
            dup_txcsr[dup] &= ~TXCSR_M_TXDONE;
            dup_clr_txint (dup);  /* clear any pending interrupts */
            if (dup_txcsr[dup] & TXCSR_M_SEND) {
                dup_txcsr[dup] |= TXCSR_M_TXACT;
                sim_activate (dup_units+dup, dup_wait[dup]);
            }
        } else {
            dup_txdbuf[dup] &= ~TXDBUF_DPV_WRITEABLE;
            dup_txdbuf[dup] |= (data & TXDBUF_DPV_WRITEABLE);
            dup_txcsr[dup] &= ~TXCSR_M_DPV_TBEMPTY;
            dup_clr_txint (dup);  /* clear any pending interrupts */
            if (dup_txcsr[dup] & TXCSR_M_DPV_SEND) {
                dup_txcsr[dup] |= TXCSR_M_DPV_TXACT;
                sim_activate (dup_units+dup, dup_wait[dup]);
                /* Go ahead not supported */
            }
        }
        break;
    }

sim_debug(DBG_REG, DUPDPTR, "dup_wr(PA=0x%08X [%s], data=0x%X) ", PA, dup_wr_regs[(PA >> 1) & 03], data);
sim_debug_bits(DBG_REG, DUPDPTR, ((UNIBUS) ? bitdefs[(PA >> 1) & 03] : dpv_bitdefs[(PA >> 1) & 03]),
               (uint32)orig_val, (uint32)regs[(PA >> 1) & 03][dup], TRUE);
dup_get_modem (dup);
return SCPE_OK;
}

static t_stat dup_set_modem (int32 dup, int32 rxcsr_bits)
{
int32 bits_to_set, bits_to_clear;

if ((rxcsr_bits & (RXCSR_M_DTR | RXCSR_M_RTS)) == (dup_rxcsr[dup] & (RXCSR_M_DTR | RXCSR_M_RTS)))
    return SCPE_OK;
bits_to_set = ((rxcsr_bits & RXCSR_M_DTR) ? TMXR_MDM_DTR : 0) | ((rxcsr_bits & RXCSR_M_RTS) ? TMXR_MDM_RTS : 0);
bits_to_clear = (~bits_to_set) & (TMXR_MDM_DTR | TMXR_MDM_RTS);
tmxr_set_get_modem_bits (dup_desc.ldsc+dup, bits_to_set, bits_to_clear, NULL);
return SCPE_OK;
}

static t_stat dup_get_modem (int32 dup)
{
int32 modem_bits;
uint16 old_rxcsr = dup_rxcsr[dup];
int32 old_rxcsr_a_modem_bits, new_rxcsr_a_modem_bits, old_rxcsr_b_modem_bits, new_rxcsr_b_modem_bits;
TMLN *lp = &dup_desc.ldsc[dup];
t_bool new_modem_change = FALSE;

if (UNIBUS) {
    if (dup_W5[dup])
        old_rxcsr_a_modem_bits = dup_rxcsr[dup] & (RXCSR_M_RING | RXCSR_M_CTS | RXCSR_M_DSR | RXCSR_M_DCD);
    else
        old_rxcsr_a_modem_bits = dup_rxcsr[dup] & (RXCSR_M_RING | RXCSR_M_CTS);
    if (dup_W6[dup])
        old_rxcsr_b_modem_bits = dup_rxcsr[dup] & RXCSR_B_MODEM_BITS;
    else
        old_rxcsr_b_modem_bits = 0;
    tmxr_set_get_modem_bits (lp, 0, 0, &modem_bits);
    if (dup_W5[dup])
        new_rxcsr_a_modem_bits = (((modem_bits & TMXR_MDM_RNG) ? RXCSR_M_RING : 0) |
                                  ((modem_bits & TMXR_MDM_CTS) ? RXCSR_M_CTS : 0) |
                                  ((modem_bits & TMXR_MDM_DSR) ? RXCSR_M_DSR : 0) |
                                  ((modem_bits & TMXR_MDM_DCD) ? RXCSR_M_DCD : 0));
    else
        new_rxcsr_a_modem_bits = (((modem_bits & TMXR_MDM_RNG) ? RXCSR_M_RING : 0) |
                                  ((modem_bits & TMXR_MDM_CTS) ? RXCSR_M_CTS : 0));
    if (dup_W6[dup])
        new_rxcsr_b_modem_bits = (((modem_bits & TMXR_MDM_DSR) ? RXCSR_M_DSR : 0) |
                                  ((modem_bits & TMXR_MDM_DCD) ? RXCSR_M_DCD : 0));
    else
        new_rxcsr_b_modem_bits = 0;
    dup_rxcsr[dup] &= ~(RXCSR_A_MODEM_BITS | RXCSR_B_MODEM_BITS);
    dup_rxcsr[dup] |= new_rxcsr_a_modem_bits | new_rxcsr_b_modem_bits;
    if (old_rxcsr_a_modem_bits != new_rxcsr_a_modem_bits) {
        dup_rxcsr[dup] |= RXCSR_M_DSCHNG;
        new_modem_change = TRUE;
    }
    if (old_rxcsr_b_modem_bits != new_rxcsr_b_modem_bits) {
        dup_rxcsr[dup] |= RXCSR_M_BDATSET;
        new_modem_change = TRUE;
    }
    if (new_modem_change) {
        sim_debug(DBG_MDM, DUPDPTR, "dup_get_modem() - Modem Signal Change ");
        sim_debug_bits(DBG_MDM, DUPDPTR, dup_rxcsr_bits, (uint32)old_rxcsr, (uint32)dup_rxcsr[dup], TRUE);
    }
    if (dup_modem_change_callback[dup] && new_modem_change)
        dup_modem_change_callback[dup](dup);
    if ((dup_rxcsr[dup] & RXCSR_M_DSCHNG) &&
        ((dup_rxcsr[dup] & RXCSR_M_DSCHNG) != (old_rxcsr & RXCSR_M_DSCHNG)) &&
        (dup_rxcsr[dup] & RXCSR_M_DSCIE))
        dup_set_rxint (dup);
} else {
    old_rxcsr_a_modem_bits = dup_rxcsr[dup] & (RXCSR_M_DPV_RING | RXCSR_M_DPV_CTS | RXCSR_M_DPV_DSR | RXCSR_M_DPV_DCD);
    tmxr_set_get_modem_bits (lp, 0, 0, &modem_bits);
    new_rxcsr_a_modem_bits = (((modem_bits & TMXR_MDM_RNG) ? RXCSR_M_DPV_RING : 0) |
                              ((modem_bits & TMXR_MDM_CTS) ? RXCSR_M_DPV_CTS : 0) |
                              ((modem_bits & TMXR_MDM_DSR) ? RXCSR_M_DPV_DSR : 0) |
                              ((modem_bits & TMXR_MDM_DCD) ? RXCSR_M_DCD : 0));
    dup_rxcsr[dup] &= ~(RXCSR_DPV_MODEM_BITS);
    dup_rxcsr[dup] |= new_rxcsr_a_modem_bits;
    if (old_rxcsr_a_modem_bits != new_rxcsr_a_modem_bits) {
        dup_rxcsr[dup] |= RXCSR_M_DPV_DSCHNG;
        new_modem_change = TRUE;
    }
    if (new_modem_change) {
        sim_debug(DBG_MDM, DUPDPTR, "dup_get_modem() - Modem Signal Change ");
        sim_debug_bits(DBG_MDM, DUPDPTR, dpv_rxcsr_bits, (uint32)old_rxcsr, (uint32)dup_rxcsr[dup], TRUE);
    }
    if (dup_modem_change_callback[dup] && new_modem_change)
        dup_modem_change_callback[dup](dup);
    if ((dup_rxcsr[dup] & RXCSR_M_DPV_DSCHNG) &&
        ((dup_rxcsr[dup] & RXCSR_M_DPV_DSCHNG) != (old_rxcsr & RXCSR_M_DPV_DSCHNG)) &&
        (dup_rxcsr[dup] & RXCSR_M_DPV_DSCIE))
        dup_set_rxint (dup);
}
return SCPE_OK;
}

/*
 * Public routines for use by other devices (i.e. KDP11)
 */

int32 dup_csr_to_linenum (int32 CSRPA)
{
DEVICE *dptr = DUPDPTR;
DIB *dib = (DIB *)dptr->ctxt;

CSRPA += IOPAGEBASE;
if ((dib->ba > (uint32)CSRPA) || ((uint32)CSRPA > (dib->ba + dib->lnt)) || (DUPDPTR->flags & DEV_DIS))
    return -1;

return ((uint32)CSRPA - dib->ba)/IOLN_DUP;
}

void dup_set_callback_mode (int32 dup, PACKET_DATA_AVAILABLE_CALLBACK receive, PACKET_TRANSMIT_COMPLETE_CALLBACK transmit, MODEM_CHANGE_CALLBACK modem)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return;
dup_rcv_packet_data_callback[dup] = receive;
dup_xmt_complete_callback[dup] = transmit;
dup_modem_change_callback[dup] = modem;
}

int32 dup_get_DCD (int32 dup)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return -1;
return (dup_rxcsr[dup] & RXCSR_M_DCD) ? 1 : 0;
}

int32 dup_get_DSR (int32 dup)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return -1;
return (dup_rxcsr[dup] & RXCSR_M_DSR) ? 1 : 0;
}

int32 dup_get_CTS (int32 dup)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return -1;
return (dup_rxcsr[dup] & RXCSR_M_CTS) ? 1 : 0;
}

int32 dup_get_RING (int32 dup)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return -1;
return (dup_rxcsr[dup] & RXCSR_M_RING) ? 1 : 0;
}

int32 dup_get_RCVEN (int32 dup)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return -1;
return (dup_rxcsr[dup] & RXCSR_M_RCVEN) ? 1 : 0;
}

t_stat dup_set_DTR (int32 dup, t_bool state)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return SCPE_IERR;
dup_set_modem (dup, (state ? RXCSR_M_DTR : 0) | (dup_rxcsr[dup] & RXCSR_M_RTS));
if (state)
    dup_rxcsr[dup] |= RXCSR_M_DTR;
else
    dup_rxcsr[dup] &= ~RXCSR_M_DTR;
dup_ldsc[dup].rcve = state;
dup_get_modem (dup);
return SCPE_OK;
}

t_stat dup_set_RTS (int32 dup, t_bool state)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return SCPE_IERR;
dup_set_modem (dup, (state ? RXCSR_M_RTS : 0) | (dup_rxcsr[dup] & RXCSR_M_DTR));
if (state)
    dup_rxcsr[dup] |= RXCSR_M_RTS;
else
    dup_rxcsr[dup] &= ~RXCSR_M_RTS;
dup_get_modem (dup);
return SCPE_OK;
}

t_stat dup_set_RCVEN (int32 dup, t_bool state)
{
uint16 orig_val;

if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return SCPE_IERR;
orig_val = dup_rxcsr[dup];
dup_rxcsr[dup] &= ~RXCSR_M_RCVEN;
dup_rxcsr[dup] |= (state ? RXCSR_M_RCVEN : 0);
if ((dup_rxcsr[dup] & RXCSR_M_RCVEN) && 
    (!(orig_val & RXCSR_M_RCVEN))) {            /* Upward transition of receiver enable */
    UNIT *uptr = dup_units + dup;

    dup_poll_svc (uptr);                        /* start any pending receive */
    }
return SCPE_OK;
}

t_stat dup_setup_dup (int32 dup, t_bool enable, t_bool protocol_DDCMP, t_bool crc_inhibit, t_bool halfduplex, uint8 station)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return SCPE_IERR;
if (!enable) {
    dup_clear(dup, TRUE);
    return SCPE_OK;
    }
if (!protocol_DDCMP) {
    return SCPE_NOFNC;              /* only DDCMP for now */
    }
if (crc_inhibit) {
    return SCPE_ARG;                /* Must enable CRC for DDCMP */
    }
dup_kmc[dup] = TRUE;     /* remember we are being used by an internal simulator device */
/* These settings reflect how RSX operates a bare DUP when used for 
   DECnet communications */
dup_clear(dup, FALSE);
dup_rxcsr[dup] |= RXCSR_M_STRSYN | RXCSR_M_RCVEN;
dup_parcsr[dup] = PARCSR_M_DECMODE | (DDCMP_SYN << PARCSR_V_ADSYNC);
dup_txcsr[dup] &= TXCSR_M_HALFDUP;
dup_txcsr[dup] |= (halfduplex ? TXCSR_M_HALFDUP : 0);
tmxr_set_line_halfduplex (dup_desc.ldsc+dup, dup_txcsr[dup] & TXCSR_M_HALFDUP);
return dup_set_DTR (dup, TRUE);
}

t_stat dup_reset_dup (int32 dup)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return SCPE_IERR;
dup_clear(dup, dup_W3[dup]);
return SCPE_OK;
}

t_stat dup_set_W3_option (int32 dup, t_bool state)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return SCPE_IERR;
dup_W3[dup] = state;
return SCPE_OK;
}

t_stat dup_set_W5_option (int32 dup, t_bool state)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return SCPE_IERR;
dup_W5[dup] = state;
return SCPE_OK;
}

t_stat dup_set_W6_option (int32 dup, t_bool state)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return SCPE_IERR;
dup_W6[dup] = state;
return SCPE_OK;
}


t_bool dup_put_msg_bytes (int32 dup, uint8 *bytes, size_t len, t_bool start, t_bool end)
{
t_bool breturn = FALSE;
int32 charmode;

if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return FALSE;

charmode = ((UNIBUS) ? (dup_parcsr[dup] & PARCSR_M_DECMODE) : (dup_parcsr[dup] & PARCSR_M_DPV_PROTSEL));

if (!tmxr_tpbusyln(&dup_ldsc[dup])) {  /* Not Busy sending? */
    if (start) {
        dup_xmtpkoffset[dup] = 0;
        dup_xmtpkdelaying[dup] = 0;
        dup_xmtpkstart[dup] = sim_grtime();
        }
    if (dup_xmtpkoffset[dup] + 2 + len > dup_xmtpksize[dup]) {
        dup_xmtpksize[dup] += 2 + (uint16)len;
        dup_xmtpacket[dup] = (uint8 *)realloc (dup_xmtpacket[dup], dup_xmtpksize[dup]);
        }
    /* Strip sync bytes at the beginning of a message */
    if (dup_kmc[dup] || charmode)
        while (len && (dup_xmtpkoffset[dup] == 0) && (bytes[0] == DDCMP_SYN)) {
            --len;
            ++bytes;
        }
    /* Insert remaining bytes into transmit buffer */
    if (len) {
        memcpy (&dup_xmtpacket[dup][dup_xmtpkoffset[dup]], bytes, len);
        dup_xmtpkoffset[dup] += (uint16)len;
        }
    if (UNIBUS)
        dup_txcsr[dup] |= TXCSR_M_TXDONE;
    else
        dup_txcsr[dup] |= TXCSR_M_DPV_TBEMPTY;
    if (dup_txcsr[dup] & TXCSR_M_TXIE)
        dup_set_txint (dup);
    /* On End of Message, insert CRC and flag delivery start */
    if (end) {
        uint16 crc16;

        if (charmode) {
            crc16 = ddcmp_crc16 (0, dup_xmtpacket[dup], dup_xmtpkoffset[dup]);
            dup_xmtpacket[dup][dup_xmtpkoffset[dup]++] = crc16 & 0xFF;
            dup_xmtpacket[dup][dup_xmtpkoffset[dup]++] = crc16 >> 8;
            if ((dup_xmtpkoffset[dup] > 8) || (dup_xmtpacket[dup][0] == DDCMP_ENQ)) {
                dup_xmtpkbytes[dup] = dup_xmtpkoffset[dup];
                ddcmp_tmxr_put_packet_ln (&dup_ldsc[dup], dup_xmtpacket[dup], dup_xmtpkbytes[dup], dup_corruption[dup]);
            }
        } else
        {
            crc16 = dup_crc_ccitt (dup_xmtpacket[dup], dup_xmtpkoffset[dup]);
            /* this crc is transmitted in big-endian order */
            dup_xmtpacket[dup][dup_xmtpkoffset[dup]++] = crc16 >> 8;
            dup_xmtpacket[dup][dup_xmtpkoffset[dup]++] = crc16 & 0xFF;
            dup_xmtpkbytes[dup] = dup_xmtpkoffset[dup];
            tmxr_put_packet_ln (&dup_ldsc[dup], dup_xmtpacket[dup], dup_xmtpkbytes[dup]);
        }
    }
    breturn = TRUE;
    }
sim_debug (DBG_TRC, DUPDPTR, "dup_put_msg_bytes(dup=%d, len=%d, start=%s, end=%s, byte=0x%02hhx) %s\n", 
           dup, (int)len, start ? "TRUE" : "FALSE", end ? "TRUE" : "FALSE", *bytes, breturn ? "Good" : "Busy");
if (breturn && (tmxr_tpbusyln (&dup_ldsc[dup]) || dup_xmtpkbytes[dup])) {
    if (dup_xmt_complete_callback[dup])
        dup_svc(dup_units+dup);
    }
return breturn;
}

t_stat dup_get_packet (int32 dup, const uint8 **pbuf, uint16 *psize)
{
if ((dup < 0) || (dup >= dup_desc.lines) || (DUPDPTR->flags & DEV_DIS))
    return SCPE_IERR;

if (*pbuf == &dup_rcvpacket[dup][0]) {
    *pbuf = NULL;
    *psize = 0;
    dup_rcvpkinoff[dup] = dup_rcvpkbytes[dup] = 0;
    dup_rxcsr[dup] &= ~RXCSR_M_RXACT;
    }
if ((dup_rcvpkinoff[dup] == 0) && (dup_rcvpkbytes[dup] != 0)) {
    *pbuf = &dup_rcvpacket[dup][0];
    *psize = dup_rcvpkbytes[dup];
    }
sim_debug (DBG_TRC, DUPDPTR, "dup_get_packet(dup=%d, psize=%d)\n", 
           dup, (int)*psize);
return SCPE_OK;
}

static t_stat dup_rcv_byte (int32 dup)
{
int32 crcoffset, charmode;

sim_debug (DBG_TRC, DUPDPTR, "dup_rcv_byte(dup=%d) - %s, byte %d of %d\n", dup, 
           (dup_rxcsr[dup] & RXCSR_M_RCVEN) ? "enabled" : "disabled",
           dup_rcvpkinoff[dup], dup_rcvpkbytes[dup]);
if (!(dup_rxcsr[dup] & RXCSR_M_RCVEN) || (dup_rcvpkbytes[dup] == 0) || (dup_rxcsr[dup] & RXCSR_M_RXDONE))
    return SCPE_OK;
if (dup_rcv_packet_data_callback[dup]) {
    sim_debug (DBG_TRC, DUPDPTR, "dup_rcv_byte(dup=%d, psize=%d) - Invoking Receive Data callback\n", 
               dup, (int)dup_rcvpkbytes[dup]);
    dup_rcv_packet_data_callback[dup](dup, dup_rcvpkbytes[dup]);
    return SCPE_OK;
    }
charmode = ((UNIBUS) ? (dup_parcsr[dup] & PARCSR_M_DECMODE) : (dup_parcsr[dup] & PARCSR_M_DPV_PROTSEL));
/* if we added trailing SYNs, don't include them in the CRC calc */
crcoffset = (dup_kmc[dup] ? 0 : TRAILING_SYNS);
dup_rxcsr[dup] |= RXCSR_M_RXACT;
if (UNIBUS)
    dup_rxdbuf[dup] &= ~(RXDBUF_M_RCRCER|RXDBUF_M_RENDMSG|RXDBUF_M_RSTRMSG);
else
    dup_rxdbuf[dup] &= ~(RXDBUF_M_DPV_RCRCER|RXDBUF_M_DPV_RENDMSG|RXDBUF_M_DPV_RSTRMSG);
dup_rxdbuf[dup] &= ~RXDBUF_M_RXDBUF;
dup_rxdbuf[dup] |= dup_rcvpacket[dup][dup_rcvpkinoff[dup]++];
dup_rxcsr[dup] |= RXCSR_M_RXDONE;
if (UNIBUS) { /* DUP */
    if (charmode) { /* DDCMP */
        if ( ((dup_rcvpkinoff[dup] == 8) || 
              (dup_rcvpkinoff[dup] >= dup_rcvpkbytes[dup]-crcoffset)) &&
             (0 == ddcmp_crc16 (0, dup_rcvpacket[dup], dup_rcvpkinoff[dup])))
            dup_rxdbuf[dup] |= RXDBUF_M_RCRCER;
        else
            dup_rxdbuf[dup] &= ~RXDBUF_M_RCRCER;
        if (dup_rcvpkinoff[dup] >= dup_rcvpkbytes[dup])
            dup_rcvpkinoff[dup] = dup_rcvpkbytes[dup] = 0;
    }
    else { /* HDLC */
        /* set End Of Message on fake Flag that was added earlier */
        if (dup_rcvpkinoff[dup] == dup_rcvpkbytes[dup]) {
            dup_rxdbuf[dup] |= RXDBUF_M_RENDMSG;
            /* check CRC only with EOM indication */
            if (0 != dup_crc_ccitt (dup_rcvpacket[dup], dup_rcvpkinoff[dup]-1))
                dup_rxdbuf[dup] |= RXDBUF_M_RCRCER;
        }
        /* set Start Of Message on first byte (primary mode only) */
        if (dup_rcvpkinoff[dup] == 1)
                dup_rxdbuf[dup] |= RXDBUF_M_RSTRMSG;
        if (dup_rcvpkinoff[dup] >= dup_rcvpkbytes[dup])
            dup_rcvpkinoff[dup] = dup_rcvpkbytes[dup] = 0;
    }
}
else { /* DPV */
    if (charmode) { /* DDCMP */
        if ( ((dup_rcvpkinoff[dup] == 6) || 
              (dup_rcvpkinoff[dup] >= dup_rcvpkbytes[dup]-crcoffset-2)) &&
             (0 == ddcmp_crc16 (0, dup_rcvpacket[dup], dup_rcvpkinoff[dup]+2)))
            dup_rxdbuf[dup] |= RXDBUF_M_DPV_RCRCER;
        else
            dup_rxdbuf[dup] &= ~RXDBUF_M_DPV_RCRCER;
        if (dup_rcvpkinoff[dup] >= dup_rcvpkbytes[dup])
            dup_rcvpkinoff[dup] = dup_rcvpkbytes[dup] = 0;
    }
    else { /* HDLC */
        /* set End Of Message on last actual message byte, excluding the CRC */
        if (dup_rcvpkinoff[dup] == dup_rcvpkbytes[dup] - 2) {
            dup_rxdbuf[dup] |= RXDBUF_M_DPV_RENDMSG;
            dup_rxcsr[dup] |= RXCSR_M_DPV_RSTARY;
            /* check CRC only with EOM indication */
            if (0 != dup_crc_ccitt (dup_rcvpacket[dup], dup_rcvpkinoff[dup] + 2))
              dup_rxdbuf[dup] |= RXDBUF_M_DPV_RCRCER;
        }
        /* set Start Of Message on first byte (primary mode only) */
        if (dup_rcvpkinoff[dup] == 1)
                dup_rxdbuf[dup] |= RXDBUF_M_DPV_RSTRMSG;
        /* DPV doesn't return the CRC bytes */
        if (dup_rcvpkinoff[dup] >= dup_rcvpkbytes[dup] - 2 )
            dup_rcvpkinoff[dup] = dup_rcvpkbytes[dup] = 0;
    }
}
if (dup_rcvpkinoff[dup] >= dup_rcvpkbytes[dup]) {
    dup_rxcsr[dup] &= ~RXCSR_M_RXACT;
   }
if (dup_rxcsr[dup] & RXCSR_M_RXIE)
    dup_set_rxint (dup);
return SCPE_OK;
}

/* service routine to delay device activity */


static t_stat dup_svc (UNIT *uptr)
{
DEVICE *dptr = DUPDPTR;
int32 dup = (int32)(uptr-dptr->units), charmode, putlen = 1;
TMLN *lp = &dup_desc.ldsc[dup];
t_bool txdone;

sim_debug(DBG_TRC, DUPDPTR, "dup_svc(dup=%d)\n", dup);
if (UNIBUS) {
    txdone = !(dup_txcsr[dup] & TXCSR_M_TXDONE);
    charmode = (dup_parcsr[dup] & PARCSR_M_DECMODE);
    if ((dup_txdbuf[dup] & TXDBUF_M_TEOM) ||
        (!charmode && ((dup_txdbuf[dup] & TXDBUF_M_TSOM))))
        putlen = 0;
}
else {
    txdone = !(dup_txcsr[dup] & TXCSR_M_DPV_TBEMPTY);
    charmode = (dup_parcsr[dup] & PARCSR_M_DPV_PROTSEL);
    if (!charmode && (dup_txdbuf[dup] & TXDBUF_M_TSOM))
        putlen = 0;
}
if (txdone && (!tmxr_tpbusyln (lp))) {
    uint8 data = dup_txdbuf[dup] & TXDBUF_M_TXDBUF;

    if (!charmode && (dup_txdbuf[dup] & TXDBUF_M_TABRT))
        /* HDLC mode abort, just reset the current TX frame back to the start */
        dup_put_msg_bytes (dup, &data, 0, TRUE, FALSE);
    else
        dup_put_msg_bytes (dup, &data, putlen, dup_txdbuf[dup] & TXDBUF_M_TSOM, (dup_txdbuf[dup] & TXDBUF_M_TEOM));
    if (tmxr_tpbusyln (lp)) { /* Packet ready to send? */
        sim_debug(DBG_TRC, DUPDPTR, "dup_svc(dup=%d) - Packet Done %d bytes\n", dup, dup_xmtpkoffset[dup]);
        }
    }
if ((tmxr_tpbusyln (lp) || dup_xmtpkbytes[dup]) && (lp->xmte || (!lp->conn))) {
    int32 start = tmxr_tpbusyln (lp) ? tmxr_tpqln (lp) + tmxr_tqln (lp) : dup_xmtpkbytes[dup];
    int32 remain = tmxr_send_buffered_data (lp);/* send any buffered data */
    if (remain) {
        sim_debug(DBG_PKT, DUPDPTR, "dup_svc(dup=%d) - Packet Transmission Stalled with %d bytes remaining\n", dup, remain);
        }
    else {
        if (!lp->conn) {
            if (dup_xmtpkoffset[dup]) {
                sim_debug(DBG_PKT, DUPDPTR, "dup_svc(dup=%d) - %d byte packet transmission with link down (dropped)\n", dup, dup_xmtpkoffset[dup]);
                }
            dup_get_modem (dup);
            }
        else {
            sim_debug(DBG_PKT, DUPDPTR, "dup_svc(dup=%d) - %d byte packet transmission complete\n", dup, dup_xmtpkbytes[dup]);
            }
        dup_xmtpkoffset[dup] = 0;
        }
    if (!tmxr_tpbusyln (lp)) {               /* Done transmitting? */
        if (((start - remain) > 0) && dup_speed[dup] && dup_xmt_complete_callback[dup] && !dup_xmtpkdelaying[dup]) { /* just done, and speed limited using packet interface? */
            dup_xmtpkdelaying[dup] = 1;
            sim_activate_notbefore (uptr, dup_xmtpkstart[dup] + (uint32)((tmr_poll*clk_tps)*((double)dup_xmtpkbytes[dup]*8)/dup_speed[dup]));
            }
        else {
            if (UNIBUS)
                dup_txcsr[dup] &= ~TXCSR_M_TXACT;   /* Set idle */
            else
                dup_txcsr[dup] &= ~TXCSR_M_DPV_TXACT;   /* Set idle */
            dup_xmtpkbytes[dup] = 0;
            dup_xmtpkdelaying[dup] = 0;
            if (dup_xmt_complete_callback[dup])
                dup_xmt_complete_callback[dup](dup, (dup_rxcsr[dup] & RXCSR_M_DCD) ? 0 : 1);
            }
        }
    }
if (dup_rxcsr[dup] & RXCSR_M_RXACT)
    dup_rcv_byte (dup);
return SCPE_OK;
}

static t_stat dup_poll_svc (UNIT *uptr)
{
    int32 dup, active, attached, charmode;

sim_debug(DBG_TRC, DUPDPTR, "dup_poll_svc()\n");

(void)tmxr_poll_conn(&dup_desc);
tmxr_poll_rx (&dup_desc);
tmxr_poll_tx (&dup_desc);
for (dup=active=attached=0; dup < dup_desc.lines; dup++) {
    TMLN *lp = &dup_desc.ldsc[dup];

    if (dup_units[dup].flags & UNIT_ATT)
        ++attached;
    if (dup_ldsc[dup].conn)
        ++active;
    dup_get_modem (dup);
    if (lp->xmte && tmxr_tpbusyln(lp)) {
        sim_debug(DBG_PKT, DUPDPTR, "dup_poll_svc(dup=%d) - Packet Transmission of remaining %d bytes restarting...\n", dup, tmxr_tpqln (lp));
        dup_svc (&dup_units[dup]);              /* Flush pending output */
        }
    if (!(dup_rxcsr[dup] & RXCSR_M_RXACT)) {
        const uint8 *buf;
        uint16 size;
        t_stat r;

        charmode = ((UNIBUS) ? (dup_parcsr[dup] & PARCSR_M_DECMODE) : (dup_parcsr[dup] & PARCSR_M_DPV_PROTSEL));

        if (charmode)
            r = ddcmp_tmxr_get_packet_ln (lp, &buf, &size, dup_corruption[dup]);
        else {
            size_t size_t_size;

            r = tmxr_get_packet_ln (lp, &buf, &size_t_size);
            size = (uint16)size_t_size;
        }
        /* In HDLC mode, we need a minimum frame size of 1 byte + CRC
           In DEC mode add some SYN bytes to the end to deal with host drivers that
           implement the DDCMP CRC performance optimisation (DDCMP V4.0 section 5.1.2).
           In HDLC mode on DUP only, add a flag because RENDMSG happens after the last actual frame character */
        if ((r == SCPE_OK) && (buf) && ((charmode) || size > 3)) {
            if (dup_rcvpksize[dup] < size + TRAILING_SYNS) {
                dup_rcvpksize[dup] = size + TRAILING_SYNS;
                dup_rcvpacket[dup] = (uint8 *)realloc (dup_rcvpacket[dup], dup_rcvpksize[dup]);
                }
            memcpy (dup_rcvpacket[dup], buf, size);
            dup_rcvpkbytes[dup] = size;
            if (!dup_kmc[dup]) {
                if (charmode) {
                    memcpy(&(dup_rcvpacket[dup][size]), tsyns, TRAILING_SYNS);
                    dup_rcvpkbytes[dup] += TRAILING_SYNS ;
                }
                else {
                    if (UNIBUS) {
                        dup_rcvpacket[dup][size] = 0x7E;
                        dup_rcvpkbytes[dup] += 1 ;
                    }
                }
            }
            dup_rcvpkinoff[dup] = 0;
            dup_rxcsr[dup] |= RXCSR_M_RXACT;
            dup_rcv_byte (dup);
            }
        }
    }
if (active)
    sim_clock_coschedule (uptr, tmxr_poll);     /* reactivate */
else {
    for (dup=0; dup < dup_desc.lines; dup++) {
        if (dup_speed[dup]/8) {
            dup_wait[dup] = (tmr_poll*clk_tps)/(dup_speed[dup]/8);
            if (dup_wait[dup] < DUP_WAIT)
                dup_wait[dup] = DUP_WAIT;
            }
        else
            dup_wait[dup] = DUP_WAIT; /* set minimum byte delay */
        }
    if (attached)
        sim_activate_after (uptr, DUP_CONNECT_POLL*1000000);/* periodic check for connections */
    }
return SCPE_OK;
}

/* Interrupt routines */

static void dup_clr_rxint (int32 dup)
{
dup_rxi = dup_rxi & ~(1 << dup);                        /* clr mux rcv int */
if (dup_rxi == 0)                                       /* all clr? */
    CLR_INT (DUPRX);
else SET_INT (DUPRX);                                   /* no, set intr */
return;
}

static void dup_set_rxint (int32 dup)
{
dup_rxi = dup_rxi | (1 << dup);                         /* set mux rcv int */
SET_INT (DUPRX);                                        /* set master intr */
sim_debug(DBG_INT, DUPDPTR, "dup_set_rxint(dup=%d)\n", dup);
return;
}

static int32 dup_rxinta (void)
{
int32 dup;

for (dup = 0; dup < dup_desc.lines; dup++) {            /* find 1st mux */
    if (dup_rxi & (1 << dup)) {
        sim_debug(DBG_INT, DUPDPTR, "dup_rxinta(dup=%d)\n", dup);
        dup_clr_rxint (dup);                            /* clear intr */
        return (dup_dib.vec + (dup * 010));             /* return vector */
        }
    }
return 0;
}

static void dup_clr_txint (int32 dup)
{
dup_txi = dup_txi & ~(1 << dup);                        /* clr mux xmt int */
if (dup_txi == 0)                                       /* all clr? */
    CLR_INT (DUPTX);
else SET_INT (DUPTX);                                   /* no, set intr */
return;
}

static void dup_set_txint (int32 dup)
{
dup_txi = dup_txi | (1 << dup);                         /* set mux xmt int */
SET_INT (DUPTX);                                        /* set master intr */
sim_debug(DBG_INT, DUPDPTR, "dup_set_txint(dup=%d)\n", dup);
return;
}

static int32 dup_txinta (void)
{
int32 dup;

for (dup = 0; dup < dup_desc.lines; dup++) {            /* find 1st mux */
    if (dup_txi & (1 << dup)) {
        sim_debug(DBG_INT, DUPDPTR, "dup_txinta(dup=%d)\n", dup);
        dup_clr_txint (dup);                            /* clear intr */
        return (dup_dib.vec + 4 + (dup * 010));         /* return vector */
        }
    }
return 0;
}

/* Device reset */

static t_stat dup_clear (int32 dup, t_bool flag)
{
sim_debug(DBG_TRC, DUPDPTR, "dup_clear(dup=%d,flag=%d)\n", dup, flag);

dup_rxdbuf[dup] = 0;                                    /* silo empty */
dup_txdbuf[dup] = 0;
dup_parcsr[dup] = 0;                                    /* no params */
dup_txcsr[dup] = ((UNIBUS) ? TXCSR_M_TXDONE : TXCSR_M_DPV_TBEMPTY);    /* clear CSR */
dup_wait[dup] = DUP_WAIT;                               /* initial/default byte delay */
if (flag) {                                             /* INIT? clr all */
    dup_rxcsr[dup] = 0;
    dup_set_modem (dup, dup_rxcsr[dup]);                /* push change out to line */
    }
else
    dup_rxcsr[dup] &= ~(RXCSR_M_DTR|RXCSR_M_RTS);       /* else save dtr & rts */
dup_clr_rxint (dup);                                    /* clear int */
dup_clr_txint (dup);
if (!dup_ldsc[dup].conn)                                /* set xmt enb */
    dup_ldsc[dup].xmte = 1;
dup_ldsc[dup].rcve = 0;                                 /* clr rcv enb */
return SCPE_OK;
}

static t_stat dup_reset (DEVICE *dptr)
{
t_stat r;
int32 i, ndev, attached = 0;

sim_debug(DBG_TRC, dptr, "dup_reset()\n");

dup_desc.packet = TRUE;
dup_desc.buffered = 16384;
if ((UNIBUS) && (dptr == &dpv_dev)) {
    if (!(dptr->flags & DEV_DIS)) {
        sim_printf ("Can't enable Qbus device on Unibus system\n");
        dptr->flags |= DEV_DIS;
        return SCPE_ARG;
        }
    return SCPE_OK;
    }

if ((!UNIBUS) && (dptr == &dup_dev)) {
    if (!(dptr->flags & DEV_DIS)) {
        sim_printf ("Can't enable Unibus device on Qbus system\n");
        dptr->flags |= DEV_DIS;
        return SCPE_ARG;
        }
    return SCPE_OK;
    }

if (dup_ldsc == NULL) {                                 /* First time startup */
    dup_desc.ldsc = dup_ldsc = (TMLN *)calloc (dup_desc.lines, sizeof(*dup_ldsc));
    for (i = 0; i < dup_desc.lines; i++) {              /* init each line */
        dup_units[i] = dup_unit_template;
        if (dup_units[i].flags & UNIT_ATT)
            ++attached;
        }
    dup_units[dup_desc.lines] = dup_poll_unit_template;
    /* Initialize to standard factory Option Jumper Settings and no associated KMC */
    for (i = 0; i < DUP_LINES; i++) {
        dup_W3[i] = TRUE;
        dup_W5[i] = FALSE;
        dup_W6[i] = TRUE;
        dup_kmc[i] = FALSE;
        }
    }
for (i = 0; i < dup_desc.lines; i++) {                  /* init each line */
    dup_clear (i, TRUE);
    if (dup_units[i].flags & UNIT_ATT)
        ++attached;
    }
dup_rxi = dup_txi = 0;                                  /* clr master int */
CLR_INT (DUPRX);
CLR_INT (DUPTX);
tmxr_set_modem_control_passthru (&dup_desc);            /* We always want Modem Control */
dup_desc.notelnet = TRUE;                               /* We always want raw tcp socket */
dup_desc.dptr = DUPDPTR;                                /* Connect appropriate device */
dup_desc.uptr = dup_units+dup_desc.lines;               /* Identify polling unit */
sim_cancel (dup_units+dup_desc.lines);                  /* stop poll */
ndev = ((dptr->flags & DEV_DIS)? 0: dup_desc.lines );
r = auto_config (dptr->name, ndev);                     /* auto config */
if ((r == SCPE_OK) && (attached))
    sim_activate_after (dup_units+dup_desc.lines, DUP_CONNECT_POLL*1000000);/* start poll */
return r;
}

static t_stat dup_attach (UNIT *uptr, CONST char *cptr)
{
t_stat r;
DEVICE *dptr = DUPDPTR;
int32 dup = (int32)(uptr-dptr->units);
char attach_string[512];

if (!cptr || !*cptr)
    return SCPE_ARG;
if (!(uptr->flags & UNIT_ATTABLE))
    return SCPE_NOATT;
sprintf (attach_string, "Line=%d,%s", dup, cptr);
r = tmxr_open_master (&dup_desc, attach_string);                 /* open master socket */
free (uptr->filename);
uptr->filename = tmxr_line_attach_string(&dup_desc.ldsc[dup]);
if (r != SCPE_OK)                                       /* error? */
    return r;
uptr->flags |= UNIT_ATT;
sim_activate_after (dup_units+dup_desc.lines, DUP_CONNECT_POLL*1000000);/* start poll */
return r;
}

static t_stat dup_detach (UNIT *uptr)
{
DEVICE *dptr = DUPDPTR;
int32 dup = (int32)(uptr-dptr->units);
TMLN *lp = &dup_ldsc[dup];
int32 i, attached;
t_stat r;

if (!(uptr->flags & UNIT_ATT))                          /* attached? */
    return SCPE_OK;
sim_cancel (uptr);
uptr->flags &= ~UNIT_ATT;
for (i=attached=0; i<dup_desc.lines; i++)
    if (dup_dev.units[i].flags & UNIT_ATT)
        ++attached;
if (!attached)
    sim_cancel (dup_units+dup_desc.lines);              /* stop poll on last detach */
r = tmxr_detach_ln (lp);
free (uptr->filename);
uptr->filename = NULL;
free (dup_rcvpacket[dup]);
dup_rcvpacket[dup] = NULL;
dup_rcvpksize[dup] = 0;
dup_rcvpkbytes[dup] = 0;
free (dup_xmtpacket[dup]);
dup_xmtpacket[dup] = NULL;
dup_xmtpksize[dup] = 0;
dup_xmtpkoffset[dup] = 0;
return r;
}

/* SET/SHOW SPEED processor */

static t_stat dup_showspeed (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
DEVICE *dptr = DUPDPTR;
int32 dup = (int32)(uptr-dptr->units);

if (dup_speed[dup])
    fprintf(st, "speed=%d bits/sec", dup_speed[dup]);
else
    fprintf(st, "speed=0 (unrestricted)");
return SCPE_OK;
}

static t_stat dup_setspeed (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
DEVICE *dptr = DUPDPTR;
int32 dup = (int32)(uptr-dptr->units);
t_stat r;
int32 newspeed;

if (cptr == NULL)
    return SCPE_ARG;
newspeed = (int32) get_uint (cptr, 10, 100000000, &r);
if (r != SCPE_OK)
    return r;
dup_speed[dup] = newspeed;
return SCPE_OK;
}

/* SET/SHOW CORRUPTION processor */

static t_stat dup_showcorrupt (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
DEVICE *dptr = DUPDPTR;
int32 dup = (int32)(uptr-dptr->units);

if (dup_corruption[dup])
    fprintf(st, "Corruption=%d milligulps (%.1f%% of messages processed)", dup_corruption[dup], ((double)dup_corruption[dup])/10.0);
else
    fprintf(st, "No Corruption");
return SCPE_OK;
}

static t_stat dup_setcorrupt (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
DEVICE *dptr = DUPDPTR;
int32 dup = (int32)(uptr-dptr->units);
t_stat r;
int32 appetite;

if (cptr == NULL)
    return SCPE_ARG;
appetite = (int32) get_uint (cptr, 10, 999, &r);
if (r != SCPE_OK)
    return r;
dup_corruption[dup] = appetite;
return SCPE_OK;
}

/* SET/SHOW W3 processor */

static t_stat dup_show_W3 (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
DEVICE *dptr = DUPDPTR;
int32 dup = (int32)(uptr-dptr->units);

if (dup_W3[dup])
    fprintf(st, "W3 Jumper Installed");
else
    fprintf(st, "W3 Jumper Removed");
return SCPE_OK;
}

static t_stat dup_set_W3 (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
DEVICE *dptr = DUPDPTR;
int32 dup = (int32)(uptr-dptr->units);

dup_W3[dup] = val;
return SCPE_OK;
}

/* SET/SHOW W5 processor */

static t_stat dup_show_W5 (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
DEVICE *dptr = DUPDPTR;
int32 dup = (int32)(uptr-dptr->units);

if (dup_W5[dup])
    fprintf(st, "W5 Jumper Installed");
else
    fprintf(st, "W5 Jumper Removed");
return SCPE_OK;
}

static t_stat dup_set_W5 (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
DEVICE *dptr = DUPDPTR;
int32 dup = (int32)(uptr-dptr->units);

dup_W5[dup] = val;
return SCPE_OK;
}

/* SET/SHOW W6 processor */

static t_stat dup_show_W6 (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
DEVICE *dptr = DUPDPTR;
int32 dup = (int32)(uptr-dptr->units);

if (dup_W6[dup])
    fprintf(st, "W6 Jumper Installed");
else
    fprintf(st, "W6 Jumper Removed");
return SCPE_OK;
}

static t_stat dup_set_W6 (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
DEVICE *dptr = DUPDPTR;
int32 dup = (int32)(uptr-dptr->units);

dup_W6[dup] = val;
return SCPE_OK;
}

/* SET LINES processor */

static t_stat dup_setnl (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
int32 newln, l;
uint32 i;
t_stat r;
DEVICE *dptr = DUPDPTR;

for (i=0; i<dptr->numunits; i++)
    if (dptr->units[i].flags&UNIT_ATT)
        return SCPE_ALATT;
if (cptr == NULL)
    return SCPE_ARG;
newln = (int32) get_uint (cptr, 10, DUP_LINES, &r);
if ((r != SCPE_OK) || (newln == dup_desc.lines))
    return r;
if (newln == 0)
    return SCPE_ARG;
sim_cancel (dup_units + dup_desc.lines);
dup_dib.lnt = newln * IOLN_DUP;                         /* set length */
dup_desc.ldsc = dup_ldsc = (TMLN *)realloc(dup_ldsc, newln*sizeof(*dup_ldsc));
for (l=dup_desc.lines; l < newln; l++) {
    memset (&dup_ldsc[l], 0, sizeof(*dup_ldsc));
    dup_units[l] = dup_unit_template;
    }
dup_units[newln] = dup_poll_unit_template;
dup_desc.lines = newln;
dup_desc.uptr = dptr->units + newln;                /* Identify polling unit */
dptr->numunits = newln + 1;
return dup_reset (dptr);                            /* setup lines and auto config */
}

/*
 * Finding a definitive definition of the correct HDLC CRC is not easy. This
 * one is the same calculation as in a couple of good quality public examples
 * that both agree with each other, so hopefully it's the correct one.
 */
uint16 dup_crc_ccitt (uint8 const *bytes, uint32 len)
{  static uint16 const crc_ccitt_lookup[256] = {
    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
    0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
    0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
    0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
    0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
    0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
    0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
    0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
    0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
    0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
    0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
    0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
    0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
    0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
    0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
    0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
    0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
    0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
    0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
    0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
    0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
    0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
    0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
    0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
    0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
    0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
    0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
    0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
    0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
    0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
    0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
    0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
};
    uint32 i = 0;
    uint16 crc = 0xffff;

    while (i++ < len)
        crc = ((crc << 8) ^ crc_ccitt_lookup[(crc >> 8) ^ *bytes++]);

/*    sim_debug (DBG_TRC, DUPDPTR, "dup_crc_ccitt (len=%d, crc=0x%04x)\n", 
      (int)len, crc); */

    return crc;
}

static t_stat dup_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
const char helpString[] =
 /* The '*'s in the next line represent the standard text width of a help line */
     /****************************************************************************/
    " The %D11 is a single-line, program controlled, double buffered\n"
    " communications device designed to interface the %1s system to a\n"
    " serial synchronous line. The original hardware is capable of handling\n"
    " a wide variety of protocols, including byte oriented protocols, such\n"
    " as DDCMP and BISYNC and bit-oriented protocols such as SDLC, HDLC\n"
    " and ADCCP.  The emulated device supports connections\n"
    " using the DDCMP or HDLC protocols.\n"
    " The %D11 is ideally suited for interfacing the %1s system\n"
    " to medium-speed synchronous lines for remote batch, remote data\n"
    " collection, remote concentration and network applications. Multiple\n"
    " %D11's on a %1s allow its use in applications requiring several\n"
    " synchronous lines.\n\n"
    " The %D11 is capable of transmitting data at the maximum speed of\n"
    " 9600 baud.  The emulated device can move data at significantly faster\n"
    " data rates.  The maximum emulated rate is dependent on the host CPU's\n"
    " available cycles.\n"
    "1 Hardware Description\n"
    " The %1s %D11 consists of a synchronous line\n"
    " unit module.\n"
    "2 $Registers\n"
    "\n"
    " These registers contain the emulated state of the device.  These values\n"
    " don't necessarily relate to any detail of the original device being\n"
    " emulated but are merely internal details of the emulation.\n"
    "1 Configuration\n"
    " A %D device is configured with various simh SET and ATTACH commands\n"
    "2 $Set commands\n"
    "3 Lines\n"
    " A maximum of %2s %D11 devices can be emulated concurrently in the %S\n"
    " simulator. The number of simulated %D devices or lines can be\n"
    " specified with command:\n"
    "\n"
    "+sim> SET %D LINES=n\n"
    "3 Peer\n"
    " To set the host and port to which data is to be transmitted use the\n"
    " following command:\n"
    "\n"
    "+sim> SET %U PEER=host:port\n"
    "3 Connectpoll\n"
    " The minimum interval between attempts to connect to the other side is set\n"
    " using the following command:\n"
    "\n"
    "+sim> SET %U CONNECTPOLL=n\n"
    "\n"
    " Where n is the number of seconds. The default is %3s seconds.\n"
    "3 Speed\n"
    " If you want to experience the actual data rates of the physical hardware\n"
    " you can set the bit rate of the simulated line can be set using the\n"
    " following command:\n"
    "\n"
    "+sim> SET %U SPEED=n\n"
    "\n"
    " Where n is the number of data bits per second that the simulated line\n"
    " runs at.  In practice this is implemented as a delay while transmitting\n"
    " bytes to the socket.  Use a value of zero to run at full speed with no\n"
    " artificial throttling.\n"
    "3 Corruption\n"
    " Corruption Troll - the DDCMP emulation includes the ability to enable a\n"
    " process that will intentionally drop or corrupt some messages.  This\n"
    " emulates the less-than-perfect communications lines encountered in the\n"
    " real world, and enables network monitoring software to see non-zero error\n"
    " counters.\n"
    "\n"
    " The troll selects messages with a probablility selected by the SET %U\n"
    " CORRUPT command.  The units are 0.1%%; that is, a value of 1 means that\n"
    " every message has a 1/1000 chance of being selected to be corrupted\n"
    " or discarded.\n"
     /****************************************************************************/
#define DUP_HLP_ATTACH "Configuration Attach"
    "2 Attach\n"
    " The communication line performs input and output through a TCP session\n"
    " (or UDP session) connected to a user-specified port.  The ATTACH command\n"
    " specifies the port to be used as well as the peer address:\n"
    "\n"
    "+sim> ATTACH %U {interface:}port{,UDP},Connect=peerhost:port\n"
    "\n"
    " where port is a decimal number between 1 and 65535 that is not being\n"
    " used for other TCP/IP activities.\n"
    "\n"
    " Specifying symmetric attach configuration (with both a listen port and\n"
    " a peer address) will cause the side receiving an incoming\n"
    " connection to validate that the connection actually comes from the\n"
    " connecction destination system.\n"
    " A symmetric attach configuration is required when using UDP packet\n"
    " transport.\n"
    "\n"
    " The default connection uses TCP transport between the local system and\n"
    " the peer.  Alternatively, UDP can be used by specifying UDP on the\n"
    " ATTACH command. \n"
    "\n"
    " Communication may alternately use the DDCMP synchronous framer device.\n"
    " The DDCMP synchronous device is a USB device that can send and\n"
    " receive DDCMP frames over either RS-232 or coax synchronous lines.\n"
    " Refer to https://github.com/pkoning2/ddcmp for documentation.\n"
    "\n"
    "+sim> ATTACH %U SYNC=ifname:mode:speed\n"
    "\n"
    " Communicate via the synchronous DDCMP framer interface \"ifname\", \n"
    " and framer mode \"mode\" -- one of INTEGRAL, RS232_DTE, or\n"
    " RS232_DCE.  The \"speed\" argument is the bit rate for the line.\n"
    " You can use \"SHOW SYNC\" to see the list of synchronous DDCMP devices.\n"
    "2 Examples\n"
    " To configure two simulators to talk to each other use the following\n"
    " example:\n"
    " \n"
    " Machine 1\n"
    "+sim> SET %D ENABLE\n"
    "+sim> ATTACH %U 1111,connect=LOCALHOST:2222\n"
    " \n"
    " Machine 2\n"
    "+sim> SET %D ENABLE\n"
    "+sim> ATTACH %U 2222,connect=LOCALHOST:1111\n"
    "\n"
    " To communicate with an \"integral modem\" DMC or similar, at 56 kbps:\n"
    "+sim> ATTACH %U SYNC=sync0:INTEGRAL:56000\n"
    "1 Monitoring\n"
    " The %D device and %U line configuration and state can be displayed with\n"
    " one of the available show commands.\n"
    "2 $Show commands\n"
    "1 Diagnostics\n"
    " Corruption Troll - the DDCMP emulation includes a process that will\n"
    " intentionally drop or corrupt some messages.  This emulates the\n"
    " less-than-perfect communications lines encountered in the real world,\n"
    " and enables network monitoring software to see non-zero error counters.\n"
    "\n"
    " The troll selects messages with a probablility selected by the SET %U\n"
    " CORRUPT command.  The units are 0.1%%; that is, a value of 1 means that\n"
    " every message has a 1/1000 chance of being selected to be corrupted\n"
    " or discarded.\n"
    "1 Restrictions\n"
    " Real hardware synchronous connections could operate in Multi-Point mode.\n"
    " Multi-Point mode was a way of sharing a single wire with multiple\n"
    " destination systems or devices.  Multi-Point mode is not currently\n"
    " emulated by this or other simulated synchronous devices.\n"
    "\n"
    "1 Implementation\n"
    " A real %D11 transports host generated protocol implemented data via a\n"
    " synchronous connection, the emulated device makes a TCP (or UDP)\n"
    " connection to another emulated device which either speaks DDCMP/HDLC over the\n"
    " TCP/UDP connection directly, or interfaces to a simulated computer where the\n"
    " operating system speaks the DDCMP protocol on the wire.\n"
    "\n"
    " The %D11 can be used for point-to-point DDCMP connections carrying\n"
    " DECnet, X.25 and other types of networking, e.g. from ULTRIX or DSM.\n"
    "1 Debugging\n"
    " The simulator has a number of debug options, these are:\n"
    "\n"
    "++REG     Shows whenever a CSR is programatically read or written\n"
    "++++and the current value.\n"
    "++INT     Shows Interrupt activity.\n"
    "++PKT     Shows Packet activity.\n"
    "++XMT     Shows Transmitted data.\n"
    "++RCV     Shows Received data.\n"
    "++MDM     Shows Modem Signal Transitions.\n"
    "++CON     Shows connection activities.\n"
    "++TRC     Shows routine call traces.\n"
    "++ASY     Shows Asynchronous activities.\n"
    "\n"
    " To get a full trace use\n"
    "\n"
    "+sim> SET %D DEBUG\n"
    "\n"
    " However it is recommended to use the following when sending traces:\n"
    "\n"
    "+sim> SET %D DEBUG=REG;PKT;XMT;RCV;CON\n"
    "\n"
    "1 Related Devices\n"
    " The %D11 can facilitate communication with other simh simulators which\n"
    " have emulated synchronous network devices available.  These include\n"
    " the following:\n"
    "\n"
    "++DUP11*       Unibus PDP11 simulators\n"
    "++DPV11*       Qbus PDP11 simulators\n"
    "++KDP11*       Unibus PDP11 simulators and PDP10 simulators\n"
    "++DMR11        Unibus PDP11 simulators and Unibus VAX simulators\n"
    "++DMC11        Unibus PDP11 simulators and Unibus VAX simulators\n"
    "++DMP11        Unibus PDP11 simulators and Unibus VAX simulators\n"
    "++DMV11        Qbus VAX simulators\n"
    "\n"
    "++* Indicates systems which have OS provided DDCMP implementations.\n"
    ;
char busname[16];
char devcount[16];
char connectpoll[16];

sprintf (busname, UNIBUS ? "Unibus" : "Qbus");
sprintf (devcount, "%d", DUP_LINES);
sprintf (connectpoll, "%d", DUP_CONNECT_POLL);

return scp_help (st, dptr, uptr, flag, helpString, cptr, busname, devcount, connectpoll);
}

static t_stat dup_help_attach (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
return dup_help (st, dptr, uptr, flag, DUP_HLP_ATTACH);
}

static const char *dup_description (DEVICE *dptr)
{
return (UNIBUS) ? "DUP11 bit synchronous interface" :
                  "DPV11 bit synchronous interface";
}
