/* pdp11_dmc.c: DMC11/DMR11/DMP11/DMV11 Emulation
  ------------------------------------------------------------------------------

   Copyright (c) 2011, Robert M. A. Jarratt, 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
   THE AUTHOR 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 the author shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from the author.

  ------------------------------------------------------------------------------

  Written by Mark Pizzolato based on an original version by Robert Jarratt

  Modification history:

  25-Jan-13  RJ   Error checking for already attached in dmc_settype() fixed.
  25-Jan-13  RJ   If attach when already attached then detach first.
  23-Jan-13  RJ   Don't do anything if not attached. See https://github.com/simh/simh/issues/28
  23-Jan-13  RJ   Clock co-scheduling move to generic framework (from Mark Pizzolato)
  21-Jan-13  RJ   Added help.
  15-Jan-13  RJ   Contribution from Paul Koning:
                  Support for RSTS using the ROM INPUT (ROM I) command to get
                  the DMC11 to report DSR status.
                  Don't accept any data from the peer until a buffer has been
                  made available.
  04-Aug-13  MP   Massive Rewrite/Restructure to implement the DDCMP wire 
                  protocol for interoperation with other sync devices (DUP11, 
                  KDP, etc.)

  ------------------------------------------------------------------------------


I/O is done through sockets so that the remote system can be on the same 
host machine. The device starts polling for incoming connections when it 
receives its first read buffer. The device opens the connection for writing 
when it receives the first write buffer.

Transmit and receive buffers are added to their respective queues and the 
polling method in dmc_poll_svc() checks for input and sends any output.

Tested with two diagnostics. To run the diagnostics set the default 
directory to SYS$MAINTENANCE, run ESSAA and then configure it for the 
DMC-11 with the following commands:

The above commands can be put into a COM file in SYS$MAINTENANCE (works 
on VMS 3.0 but not 4.6, not sure why).

ATT DW780 SBI DW0 3 4
ATT DMC11 DW0 XMA0 760070 300 5
SELECT XMA0
(if putting these into a COM file to be executed by ESSAA add a "DS> " prefix)


The first is EVDCA which takes no parameters. Invoke it with the command 
R EVDCA. This diagnostic uses the DMC-11 loopback functionality and the 
transmit port is not used when LU LOOP is enabled. Seems to work only under 
later versions of VMS such as 4.6, does not work on 3.0.  It does not work
under VMS 3.x since in that environment, no receive buffers are ever made
available to the device while testing.

The second is EVDMC, invoke this with the command R EVDMC. For this I used 
the following commands inside the diagnostic:

RUN MODE=TRAN on one machine
RUN MODE=REC on the other

or using loopback mode:

SET TRANSMIT=CCITT/SIZE=25/COPY=5
SET EXPECT=CCITT/SIZE=25/COPY=5
RUN MODE=ACTIVE/LOOP=INT/STATUS/PASS=3

You can add /PASS=n to the above commands to get the diagnostic to send and 
receive more buffers.

The other test was to configure DECnet on VMS 4.6 and do SET HOST.
*/

// TODO: Test MOP.

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

#if !defined(DMP_NUMDEVICE)
#define DMP_NUMDEVICE 1         /* Minimum number for array size DMP-11/DMV-11 devices */
#endif

#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"

#define DMC_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 */

#if !defined(DMC_NUMDEVICE)
#define DMC_NUMDEVICE 8         /* default MAX # DMC-11 devices */
#endif

#if !defined(DMP_NUMDEVICE)
#define DMP_NUMDEVICE 8         /* default MAX # DMP-11/DMV-11 devices */
#endif

#define DMC_RDX                     8


/* DMC/DMR register SEL0 */
#define DMC_SEL0_V_ITYPE     0
#define DMC_SEL0_S_ITYPE     4
#define DMC_SEL0_M_ITYPE    (((1<<DMC_SEL0_S_ITYPE)-1)<<DMC_SEL0_V_ITYPE)
#define DMC_C_TYPE_XBACC     0      /* Transmit Buffer Address/Character Count Command and Response */
#define DMC_C_TYPE_CNTL      1      /* Control In Command and Control Out Response */
#define DMC_C_TYPE_HALT      2      /* Halt In Command */
#define DMC_C_TYPE_BASEIN    3      /* Base In Command */
#define DMC_C_TYPE_RBACC     4      /* Receive Buffer Address/Character Count Command and Response */
#define DMC_SEL0_V_RQI       5
#define DMC_SEL0_M_RQI      0x0020
#define DMC_SEL0_V_IEI       6
#define DMC_SEL0_M_IEI      0x0040
#define DMC_SEL0_V_RDI       7
#define DMC_SEL0_M_RDI      0x0080
#define DMC_SEL0_V_STEPUP    8
#define DMC_SEL0_M_STEPUP   0x0100
#define DMC_SEL0_V_ROMI      9
#define DMC_SEL0_M_ROMI     0x0200
#define DMC_SEL0_V_ROMO     10
#define DMC_SEL0_M_ROMO     0x0400
#define DMC_SEL0_V_LU_LOOP  11
#define DMC_SEL0_M_LU_LOOP  0x0800
#define DMC_SEL0_V_STEPLU   12
#define DMC_SEL0_M_STEPLU   0x1000
#define DMC_SEL0_V_UDIAG    13
#define DMC_SEL0_M_UDIAG    0x2000
#define DMC_SEL0_V_MCLEAR   14
#define DMC_SEL0_M_MCLEAR   0x4000
#define DMC_SEL0_V_RUN      15
#define DMC_SEL0_M_RUN      0x8000

/* DMP/DMV register SEL0 */
#define DMP_SEL0_V_IEI       0
#define DMP_SEL0_M_IEI      0x0001
#define DMP_SEL0_V_IEO       4
#define DMP_SEL0_M_IEO      0x0010
#define DMP_SEL0_V_RQI       7
#define DMP_SEL0_M_RQI      0x0080

/* DMC/DMR register SEL2 */
#define DMC_SEL2_V_CODE      0
#define DMC_SEL2_S_CODE      3
#define DMC_SEL2_M_CODE     (((1<<DMC_SEL2_S_CODE)-1)<<DMC_SEL2_V_CODE)
#define DMC_SEL2_V_IEO       6
#define DMC_SEL2_M_IEO      0x0040
#define DMC_SEL2_V_RDO       7
#define DMC_SEL2_M_RDO      0x0080

/* DMP/DMV register SEL2 */
#define DMP_SEL2_V_CODE      0
#define DMP_SEL2_S_CODE      3
#define DMP_SEL2_M_CODE     (((1<<DMP_SEL2_S_CODE)-1)<<DMP_SEL2_V_CODE)
#define DMP_C_TYPE_RBACC     0      /* Receive Buffer Address/Character Count Command and Response */
#define DMP_C_TYPE_CNTL      1      /* Control Command or Control Response */
#define DMP_C_TYPE_MODE      2      /* Mode Definition Command or Information Response */
#define DMP_C_TYPE_UNUSED    3      /* Receive Buffer disposition (unused) Response */
#define DMP_C_TYPE_XBACC     4      /* Transmit Buffer Address/Character Count Command and Response */
#define DMP_C_TYPE_RESERVED  5      /* Reserved/Unused code */
#define DMP_C_TYPE_SENT      6      /* Buffer disposition (sent but not acknowledged) Response */
#define DMP_C_TYPE_UNSENT    7      /* Buffer disposition (not sent) Response */
#define DMP_SEL2_V_22BIT     3
#define DMP_SEL2_M_22BIT    0x0008
#define DMP_SEL2_V_RDI       4
#define DMP_SEL2_M_RDI      0x0010
#define DMP_SEL2_V_RDO       7
#define DMP_SEL2_M_RDO      0x0080

/* DMC/DMR register SEL4 */
#define DMC_SEL4_V_CAR       0
#define DMC_SEL4_M_CAR      0x0001
#define DMC_SEL4_V_STN       1
#define DMC_SEL4_M_STN      0x0002
#define DMC_SEL4_V_CTS       2
#define DMC_SEL4_M_CTS      0x0004
#define DMC_SEL4_V_DSR       3
#define DMC_SEL4_M_DSR      0x0008
#define DMC_SEL4_V_HDX       4
#define DMC_SEL4_M_HDX      0x0010
#define DMC_SEL4_V_RTS       5
#define DMC_SEL4_M_RTS      0x0020
#define DMC_SEL4_V_DTR       6
#define DMC_SEL4_M_DTR      0x0040
#define DMC_SEL4_V_RI        7
#define DMC_SEL4_M_RI       0x0080

/* SEL2 */
#define DMP_TYPE_INPUT_MASK 0x0007

/* DMC/DMR register SEL6 */
/* Bit Flags used in a Control Out command */
#define DMC_SEL6_V_NAKTRSH   0      /* NAK Threshold */
#define DMC_SEL6_M_NAKTRSH  0x0001
#define DMC_SEL6_V_TIMEOUT   1
#define DMC_SEL6_M_TIMEOUT  0x0002
#define DMC_SEL6_V_NOBUF     2
#define DMC_SEL6_M_NOBUF    0x0004
#define DMC_SEL6_V_MAINTRCV  3
#define DMC_SEL6_M_MAINTRCV 0x0008
#define DMC_SEL6_V_LOSTDATA  4
#define DMC_SEL6_M_LOSTDATA 0x0010
#define DMC_SEL6_V_DISCONN   6
#define DMC_SEL6_M_DISCONN  0x0040
#define DMC_SEL6_V_STRTRCVD  7
#define DMC_SEL6_M_STRTRCVD 0x0080
#define DMC_SEL6_V_NXM       8
#define DMC_SEL6_M_NXM      0x0100
#define DMC_SEL6_V_HALTCOMP  9
#define DMC_SEL6_M_HALTCOMP 0x0200
/* Bit Flags used in a Control In command */
#define DMC_SEL6_V_MAINT     8      /* NAK Threshold */
#define DMC_SEL6_M_MAINT    0x0100
#define DMC_SEL6_V_HDX      10
#define DMC_SEL6_M_HDX      0x0400
#define DMC_SEL6_V_LONGSTRT 11
#define DMC_SEL6_M_LONGSTRT 0x0800
/* Bit flags used in DMR modem status after RDI */
#define DMC_SEL6_V_LMODEM 11
#define DMC_SEL6_M_LMODEM 0x0800

#define DSPDSR     0x22b3    /* KMC opcode to move line unit status to SEL2 */
#define DROPDTR    0xa40b    /* KMC opcode to drop DTR */
#define UINST_CNF  0x2296    /* KMC opcode to get config switches */
#define UINST_CNF3 0x2293    /* KMC opcode ditto but to BSEL3 */
#define UINST_RROM 0x8100    /* KMC opcode to read DMC ROM, address in low bits */

#define SEL2_TYPEO_BIT 0
#define SEL2_RDO_BIT 7
#define SEL2_IEO_BIT 6
#define SEL2_OUT_IO_BIT 2
#define SEL2_LINE_BIT 8
#define SEL2_LINE_BIT_LENGTH 6
#define SEL2_PRIO_BIT 14
#define SEL2_PRIO_BIT_LENGTH 2

#define DMC_QUEUE_SIZE  7
#define DMR_QUEUE_SIZE 64
#define DMP_QUEUE_SIZE 64

struct csrs {
    uint16 *sel0;
    uint16 *sel2;
    uint16 *sel4;
    uint16 *sel6;
    uint16 *sel10;
    };

typedef struct csrs CSRS;

typedef enum {
    Uninitialised,  /* before MASTER CLEAR */
    Initialised,    /* after MASTER CLEAR */
    Running,        /* after any transmit or receive buffer has been supplied */
    Halted          /* after reciept of explicit halt input command */
    } ControllerState;

typedef enum {
    Idle,
    InputTransfer,
    OutputTransfer,
    OutputControl
    } TransferState;

/* 129 words taken from RSTS/E ECO checker listing; the rest is not
   used so we can leave it as zeroes.  */
static const uint16 hi_speed_ucode[256] = {
    063220,
    063223,
    063237,
    063232,
    061200,
    061202,
    03370,
    063130,
    076423,
    063060,
    0101414,
    0100407,
    03401,
    063231,
    010162,
    0626,
    062234,
    016403,
    016402,
    016406,
    016407,
    016401,
    010210,
    016455,
    016472,
    010067,
    016471,
    02471,
    043236,
    010022,
    016424,
    02424,
    043235,
    010240,
    016642,
    02642,
    0600,
    061221,
    0110642,
    020620,
    0173202,
    020640,
    0167203,
    010210,
    0140620,
    020620,
    0103060,
    0100463,
    0433,
    060342,
    0101047,
    0123620,
    0113246,
    023240,
    060520,
    0103451,
    010211,
    0140620,
    060601,
    0103100,
    0123400,
    01620,
    0117034,
    0100447,
    02611,
    0100447,
    0123400,
    0103505,
    0114434,
    060520,
    0103603,
    0120400,
    01620,
    0103047,
    02514,
    0100447,
    0123400,
    0102527,
    02654,
    0102124,
    01620,
    02722,
    0102524,
    02701,
    060601,
    0102130,
    0100447,
    0102140,
    02472,
    0500,
    061260,
    010177,
    016402,
    02400,
    042233,
    0114474,
    060721,
    0102130,
    02472,
    010017,
    0136500,
    0136520,
    0122560,
    0123000,
    0500,
    061260,
    02133,
    040620,
    0103164,
    010151,
    016406,
    02700,
    063161,
    0641,
    03374,
    0110727,
    03004,
    063070,
    010017,
    0731,
    0110463,
    010154,
    057310,
    057231,
    057235,
    043237,
    043232,
    063170,
    063161
};
    
/* Queue elements
 * A queue is a double-linked list of element headers.
 * The head of the queue is an element without a body.
 * A queue is empty when the only element in the queue is
 * the head.
 * Each queue has an asociated count of the elements in the
 * queue; this simplifies knowing its state.
 * Each queue also has a maximum length.
 *
 * Queues are manipulated with initqueue, insqueue, and remqueue.
 */
struct queuehdr {
    struct queuehdr     *next;
    struct queuehdr     *prev;
    struct buffer_queue *queue;
    };
typedef struct queuehdr QH;

typedef struct buffer_queue {
    QH hdr;                             /* Forward/Back Buffer pointers */
    const char * name;                  /* Queue name */
    size_t size;                        /* Maximum number of entries (0 means no limit) */
    size_t count;                       /* Current Used Count */
    struct dmc_controller *controller;  /* back pointer to the containing controller */
    } BUFFER_QUEUE;

/* Queue management */

/* Insert entry on queue after pred, if count < max.
 * Increment count.
 *   To insert at head of queue, specify &head for predecessor.
 *   To insert at tail, specify head.prev
 *
 * returns FALSE if queue is full.
 */

static t_bool insqueue (QH *entry, QH *pred)
{
if ((pred->queue->size > 0) && (pred->queue->count >= pred->queue->size))
    return FALSE;
ASSURE (entry->queue == NULL);
entry->next = pred->next;
entry->prev = pred;
entry->queue = pred->queue;
pred->next->prev = entry;
pred->next = entry;
++pred->queue->count;
return TRUE;
}

/* Remove entry from queue.
 * Decrement count.
 *  To remove from head of queue, specify head.next.
 *  To remove form tail of queue, specify head.prev.
 *
 * returns NULL if queue is empty.
 */

static void *remqueue (QH *entry)
{
if (entry->queue->count == 0)
    return NULL;
entry->prev->next = entry->next;
entry->next->prev = entry->prev;
--entry->queue->count;
entry->next = entry->prev = NULL;
entry->queue = NULL;
return (void *)entry;
}

/* Initialize a queue to empty.
 *
 * Optionally, adds a list of elements to the queue.
 * max, list and size are only used if list is non-NULL.
 *
 * Convenience macros:
 *   MAX_LIST_SIZE(q) specifies max, list and size for an array of elements q
 *   INIT_HDR_ONLY provides placeholders for these arguments when only the
 * header and count are to be initialized.
 */

#define DIM(x) (sizeof(x)/sizeof((x)[0]))
/* Convenience for initqueue() calls */
#    define MAX_LIST_SIZE(q)                    DIM(q),    (q),       sizeof(q[0])
#    define INIT_HDR_ONLY                       0,         NULL,      0

static void initqueue (QH *head, struct buffer_queue *queue, size_t max, void *list, size_t size)
{
head->next = head->prev = head;
head->queue = queue;
head->queue->count = 0;
head->queue->size = max;
if (list == NULL)
    return;
while (insqueue ((QH *)list, head->prev))
    list = (QH *)(((char *)list)+size);
return;
}

typedef enum {
    Receive,                /* Receive Buffer */
    TransmitData,           /* Transmit Buffer */
    TransmitControl         /* Transmit Control Packet */
    } BufferType;

typedef struct buffer {
    QH hdr;                   /* queue linkage */
    BufferType type;          /* 0 = Receive Buffer, 1 = Transmit Buffer */
    uint32 address;           /* unibus address of the buffer (or 0 for DDCMP control messages) */
    uint16 count;             /* size of the buffer passed to the device by the driver */
    uint8 *transfer_buffer;   /* the buffer into which data is received or from which it is transmitted*/
    int actual_bytes_transferred;/* the number of bytes from the actual block that have been read or written */
    uint32 buffer_return_time;/* time to return buffer to host */
    } BUFFER;

typedef enum {
    Halt,       /* initially */
    IStart,     /* Initiating Start */
    AStart,     /* Acknowledging Start */
    Run,        /* Start ACK received */
    Maintenance,/* off-line maintenance messages */
    All         /* Not settable, but match for any state in state table */
    } DDCMP_LinkState;

typedef struct {
    uint8 R;                /* number of the highest sequential data message received . 
                               Sent in the RESP field of data messages, ACK messages,
                               and NAK messages as acknowledgment to the other station. */
    uint8 N;                /* Number of the highest sequential data message 
                               transmitted.  Sent in the NUM field of REP 
                               messages. N is the number assigned to the last 
                               user transmit request which has been transmitted 
                               (sent in the NUM field of that data message).*/
    uint8 A;                /* Number of the highest sequential data message acknowledged.
                               Received in the RESP field of data messages, ACK messages, 
                               and NAK messages.*/
    uint8 T;                /* Number of the next data message to be transmitted.
                               When sending new data messages T will have the 
                               value N+l. When retransmitting T will be set back
                               to A+l and will advance to N+l.*/
    uint8 X;                /* Number of the last data message that has been 
                               transmitted.  When a new data message has been 
                               completely transmitted X will have the value N. 
                               When retransmitting and receiving acknowledgments 
                               asynchronously with respect to transmission, X will 
                               have some value less than or equal to N. */
    uint8 NAKed;            /* The value of R sent in the most recent NAK message.  This
                               is used to avoid sending additional NAKs when one has already
                               been sent while the remaining packets in the transmit pipeline
                               are still arriving. */
    t_bool SACK;            /* Send ACK flag.  This flag is set when either R 
                               is incremented, meaning a new sequential data 
                               message has been received which requires an ACK 
                               reply, or a REP message is received which requires 
                               an ACK reply. The SACK flag is cleared when 
                               sending either a DATA message with the latest RESP 
                               field information, an ACK with the latest RESP 
                               field information, or when the SNAK flag is set. */
                 /* 
                    The SACK and SNAK flags are mutually exclusive. At most one 
                    will be set at a given time. The events that set the SACK 
                    flag, R is increment~d or a REP is received requiring an ACK, 
                    also clear the SNAK flag. Similarly, the events that set the 
                    SNAK flag, reasons for send ing a NAK (see 5.3.7), also clear 
                    the SACK flag. Setting or clearing a flag that is already set 
                    or cleared respectively has no effect. For the SNAK flag, a 
                    reason variable (or field) is also maintained which is 
                    overwritten with the latest NAK error reason.
                    Whenever the SNAK flag is set the NAK reason variable is set 
                    to the reason for the NAK. */
    t_bool SNAK;            /* Send NAK flag.  This flag is set when a receive 
                               error occurs that requires a NAK reply. It is 
                               cleared when a NAK message is sent with the 
                               latest RESP information, or when the SACK flag
                               is set.*/
    t_bool SREP;            /* Send REP flag.  This flag is set when a reply 
                               timer expires in the running state and a REP 
                               should be sent. It is independent of the SACK 
                               and SNAK flags.*/
    uint8 *rcv_pkt;         /* Current Rcvd Packet buffer */
    uint16 rcv_pkt_size;    /* Current Rcvd Packet size */
    BUFFER *xmt_buffer;     /* Current Transmit Buffer */
    BUFFER *xmt_done_buffer;/* Just Completed Transmit Buffer */
    uint8 nak_reason;       /*  */
    uint8 nak_crc_reason;   /* CRC status for current received packet */
    DDCMP_LinkState state;  /* Current State */
    t_bool TimerRunning;    /* Timer Running Flag */
    t_bool TimeRemaining;   /* Seconds remaining before timeout (when timer running) */
    t_bool Scanning;        /* Event Scanning in progress */
    uint32 ScanningEvents;  /* Event Mask while scanning */
    t_bool RecurseScan;     /* Scan was attempted while scanning */
    uint32 RecurseEventMask;/* Mask for recursive Scan */
#define DDCMP_EVENT_XMIT_DONE 0x001
#define DDCMP_EVENT_PKT_RCVD  0x002
#define DDCMP_EVENT_TIMER     0x004
#define DDCMP_EVENT_MAINTMODE 0x008
    } DDCMP;

typedef struct control {
    struct control *next;       /* Link */
    uint16  sel6;               /* Control Out Status Flags */
    } CONTROL_OUT;

typedef enum {
    DMC,
    DMR,
    DMP
    } DEVTYPE;

typedef struct dmc_controller {
    CSRS *csrs;
    DEVICE *device;
    UNIT *unit;
    int index;                      /* Index in controller array */
    ControllerState state;
    TransferState transfer_state;   /* current transfer state (type of transfer) */
    DDCMP link;
    int transfer_type;              /* Input Command setting at start of input transfer as host changes it during transfer! */
    TMLN *line;
    BUFFER_QUEUE *rcv_queue;        /* Receive Buffer Queue */
    BUFFER_QUEUE *completion_queue; /* Transmit and Recieve Buffers waiting to pass to driver */
    BUFFER_QUEUE *xmt_queue;        /* Control and/or Data packets pending transmission */
    BUFFER_QUEUE *ack_wait_queue;   /* Data packets awaiting acknowledgement */
    BUFFER_QUEUE *free_queue;       /* Unused Buffer Queue */
    BUFFER **buffers;               /* Buffers */
    CONTROL_OUT *control_out;
    DEVTYPE dev_type;
    uint32 in_int;
    uint32 out_int;
    uint32 dmc_wr_delay;
    uint32 *baseaddr;
    uint16 *basesize;
    uint8 *modem;
    int32 *corruption_factor;
    uint32 buffers_received_from_net;
    uint32 buffers_transmitted_to_net;
    uint32 receive_buffer_output_transfers_completed;
    uint32 transmit_buffer_output_transfers_completed;
    uint32 receive_buffer_input_transfers_completed;
    uint32 transmit_buffer_input_transfers_completed;
    uint32 control_out_operations_completed;
    uint32 ddcmp_control_packets_received;
    uint32 ddcmp_control_packets_sent;
    uint32 byte_wait;                           /* rcv/xmt byte delay */
    } CTLR;

/* 
    DDCMP implementation follows the DDCMP protocol specification documented in the 
    "DECNET DIGITAL NETWORK ARCHITECTURE Digital Data Communications Message Protocol"
    Version 4.0, March 1, 1978.
    
    available on http://bitsavers.org/pdf/dec/decnet/AA-D599A-TC_DDCMP4.0_Mar78.pdf

 */

typedef void (*DDCMP_LinkAction_Routine)(CTLR *controller);

typedef t_bool (*DDCMP_Condition_Routine)(CTLR *controller);

t_bool ddcmp_UserHalt             (CTLR *controller);
t_bool ddcmp_UserStartup          (CTLR *controller);
t_bool ddcmp_UserMaintenanceMode  (CTLR *controller);
t_bool ddcmp_ReceiveStack         (CTLR *controller);
t_bool ddcmp_ReceiveStrt          (CTLR *controller);
t_bool ddcmp_TimerRunning         (CTLR *controller);
t_bool ddcmp_TimerNotRunning      (CTLR *controller);
t_bool ddcmp_TimerExpired         (CTLR *controller);
t_bool ddcmp_ReceiveMaintMessage  (CTLR *controller);
t_bool ddcmp_ReceiveAck           (CTLR *controller);
t_bool ddcmp_ReceiveNak           (CTLR *controller);
t_bool ddcmp_ReceiveRep           (CTLR *controller);
t_bool ddcmp_NUMEqRplus1          (CTLR *controller);   /* (NUM == R+1) */
t_bool ddcmp_NUMGtRplus1          (CTLR *controller);   /* (NUM > R+1) */
t_bool ddcmp_ReceiveDataMsg       (CTLR *controller);   /* Receive Data Message */
t_bool ddcmp_ReceiveMaintMsg      (CTLR *controller);   /* Receive Maintenance Message */
t_bool ddcmp_ALtRESPleN           (CTLR *controller);   /* (A < RESP <= N) */
t_bool ddcmp_ALeRESPleN           (CTLR *controller);   /* (A <= RESP <= N) */
t_bool ddcmp_RESPleAOrRESPgtN     (CTLR *controller);   /* (RESP <= A) OR (RESP > N) */
t_bool ddcmp_TltNplus1            (CTLR *controller);   /* T < N + 1 */
t_bool ddcmp_TeqNplus1            (CTLR *controller);   /* T == N + 1 */
t_bool ddcmp_ReceiveMessageError  (CTLR *controller);   
t_bool ddcmp_NumEqR               (CTLR *controller);   /* (NUM == R) */
t_bool ddcmp_NumNeR               (CTLR *controller);   /* (NUM != R) */
t_bool ddcmp_TransmitterIdle      (CTLR *controller);
t_bool ddcmp_TramsmitterBusy      (CTLR *controller);
t_bool ddcmp_SACKisSet            (CTLR *controller);
t_bool ddcmp_SACKisClear          (CTLR *controller);
t_bool ddcmp_SNAKisSet            (CTLR *controller);
t_bool ddcmp_SNAKisClear          (CTLR *controller);
t_bool ddcmp_SREPisSet            (CTLR *controller);
t_bool ddcmp_SREPisClear          (CTLR *controller);
t_bool ddcmp_UserSendMessage      (CTLR *controller);
t_bool ddcmp_LineConnected        (CTLR *controller);
t_bool ddcmp_LineDisconnected     (CTLR *controller);
t_bool ddcmp_DataMessageSent      (CTLR *controller);
t_bool ddcmp_REPMessageSent       (CTLR *controller);

void ddcmp_StartTimer             (CTLR *controller);
void ddcmp_StopTimer              (CTLR *controller);
void ddcmp_ResetVariables         (CTLR *controller);
void ddcmp_SendStrt               (CTLR *controller);
void ddcmp_SendStack              (CTLR *controller);
void ddcmp_SendAck                (CTLR *controller);
void ddcmp_SendNak                (CTLR *controller);
void ddcmp_SendRep                (CTLR *controller);
void ddcmp_SetSACK                (CTLR *controller);
void ddcmp_ClearSACK              (CTLR *controller);
void ddcmp_SetSNAK                (CTLR *controller);
void ddcmp_ClearSNAK              (CTLR *controller);
void ddcmp_SetSREP                (CTLR *controller);
void ddcmp_ClearSREP              (CTLR *controller);
void ddcmp_IncrementR             (CTLR *controller);
void ddcmp_SetAeqRESP             (CTLR *controller);
void ddcmp_SetTequalAplus1        (CTLR *controller);
void ddcmp_IncrementT             (CTLR *controller);
void ddcmp_SetNAKreason3          (CTLR *controller);
void ddcmp_SetNAKReasonCRCError   (CTLR *controller);
void ddcmp_NAKMissingPacket       (CTLR *controller);
void ddcmp_IfTleAthenSetTeqAplus1 (CTLR *controller);
void ddcmp_IfAltXthenStartTimer   (CTLR *controller);
void ddcmp_IfAgeXthenStopTimer    (CTLR *controller);
void ddcmp_Ignore                 (CTLR *controller);
void ddcmp_GiveBufferToUser       (CTLR *controller);
void ddcmp_CompleteAckedTransmits (CTLR *controller);
void ddcmp_ReTransmitMessageT     (CTLR *controller);
void ddcmp_NotifyDisconnect       (CTLR *controller);
void ddcmp_NotifyStartRcvd        (CTLR *controller);
void ddcmp_NotifyMaintRcvd        (CTLR *controller);
void ddcmp_SendDataMessage        (CTLR *controller);
void ddcmp_SendMaintMessage       (CTLR *controller);
void ddcmp_SetXSetNUM             (CTLR *controller);

typedef struct _ddcmp_state_table {
    int RuleNumber;
    DDCMP_LinkState State;
    DDCMP_Condition_Routine Conditions[10];
    DDCMP_LinkState NewState;
    DDCMP_LinkAction_Routine Actions[10];
    } DDCMP_STATETABLE;

DDCMP_STATETABLE DDCMP_TABLE[] = {
    { 0, All,         {ddcmp_UserHalt},            Halt,           {ddcmp_StopTimer}},
    { 1, Halt,        {ddcmp_UserStartup,
                       ddcmp_LineConnected},       IStart,         {ddcmp_ResetVariables,
                                                                    ddcmp_SendStrt,
                                                                    ddcmp_StopTimer}},
    { 2, Halt,        {ddcmp_UserMaintenanceMode}, Maintenance,    {ddcmp_ResetVariables}},
    { 3, Halt,        {ddcmp_ReceiveMaintMsg},     Maintenance,    {ddcmp_ResetVariables, 
                                                                    ddcmp_NotifyMaintRcvd}},
    { 4, IStart,      {ddcmp_TimerNotRunning},     IStart,         {ddcmp_StartTimer}},
    { 5, IStart,      {ddcmp_ReceiveStack},        Run,            {ddcmp_SetSACK,
                                                                    ddcmp_StopTimer}},
    { 6, IStart,      {ddcmp_ReceiveStrt},         AStart,         {ddcmp_SendStack, 
                                                                    ddcmp_StartTimer}},
    { 7, IStart,      {ddcmp_TimerExpired},        IStart,         {ddcmp_ResetVariables, 
                                                                    ddcmp_SendStrt, 
                                                                    ddcmp_StartTimer}},
    { 8, IStart,      {ddcmp_ReceiveMaintMsg},     Maintenance,    {ddcmp_ResetVariables, 
                                                                    ddcmp_NotifyMaintRcvd}},
    { 9, IStart,      {ddcmp_ReceiveMessageError}, IStart,         {ddcmp_Ignore}},
    {10, IStart,      {ddcmp_UserSendMessage,
                       ddcmp_TimerNotRunning},     IStart,         {ddcmp_StartTimer}},
    {11, IStart,      {ddcmp_LineDisconnected},    Halt,           {ddcmp_StopTimer}},
    {12, AStart,      {ddcmp_LineDisconnected},    Halt,           {ddcmp_StopTimer}},
    {13, AStart,      {ddcmp_ReceiveAck},          Run,            {ddcmp_StopTimer}},
    {14, AStart,      {ddcmp_ReceiveDataMsg},      Run,            {ddcmp_StopTimer}},
    {15, AStart,      {ddcmp_ReceiveStack},        Run,            {ddcmp_SetSACK, 
                                                                    ddcmp_StopTimer}},
    {16, AStart,      {ddcmp_TimerExpired},        AStart,         {ddcmp_SendStack, 
                                                                    ddcmp_StartTimer}},
    {17, AStart,      {ddcmp_ReceiveMaintMsg},     Maintenance,    {ddcmp_ResetVariables, 
                                                                    ddcmp_NotifyMaintRcvd}},
    {18, AStart,      {ddcmp_ReceiveMessageError}, AStart,         {ddcmp_Ignore}},
    {19, Run,         {ddcmp_LineDisconnected},    Halt,           {ddcmp_StopTimer,
                                                                    ddcmp_NotifyDisconnect,
                                                                    ddcmp_NotifyStartRcvd}},
    {20, Run,         {ddcmp_ReceiveStrt},         Halt,           {ddcmp_NotifyStartRcvd}},
    {21, Run,         {ddcmp_ReceiveMaintMsg},     Maintenance,    {ddcmp_ResetVariables, 
                                                                    ddcmp_NotifyMaintRcvd}},
    {22, Run,         {ddcmp_ReceiveStack},        Run,            {ddcmp_SetSACK}},
    {23, Run,         {ddcmp_ReceiveDataMsg,
                       ddcmp_NUMGtRplus1},         Run,            {ddcmp_NAKMissingPacket}},
    {24, Run,         {ddcmp_ReceiveDataMsg,
                       ddcmp_NUMEqRplus1},         Run,            {ddcmp_GiveBufferToUser}},
    {25, Run,         {ddcmp_ReceiveMessageError}, Run,            {ddcmp_SetSNAK,
                                                                    ddcmp_SetNAKReasonCRCError}},
    {26, Run,         {ddcmp_ReceiveRep,
                       ddcmp_NumEqR},              Run,            {ddcmp_SetSACK}},
    {27, Run,         {ddcmp_ReceiveRep,
                       ddcmp_NumNeR},              Run,            {ddcmp_SetSNAK, 
                                                                    ddcmp_SetNAKreason3}},
    {28, Run,         {ddcmp_ReceiveDataMsg,
                       ddcmp_ALtRESPleN},          Run,            {ddcmp_CompleteAckedTransmits, 
                                                                    ddcmp_SetAeqRESP, 
                                                                    ddcmp_IfTleAthenSetTeqAplus1, 
                                                                    ddcmp_IfAgeXthenStopTimer}},
    {29, Run,         {ddcmp_ReceiveAck,
                       ddcmp_ALtRESPleN},          Run,            {ddcmp_CompleteAckedTransmits, 
                                                                    ddcmp_SetAeqRESP, 
                                                                    ddcmp_IfTleAthenSetTeqAplus1, 
                                                                    ddcmp_IfAgeXthenStopTimer}},
    {30, Run,         {ddcmp_ReceiveAck,
                       ddcmp_RESPleAOrRESPgtN},    Run,            {ddcmp_Ignore}},
    {31, Run,         {ddcmp_ReceiveDataMsg,
                       ddcmp_RESPleAOrRESPgtN},    Run,            {ddcmp_Ignore}},
    {32, Run,         {ddcmp_ReceiveNak,
                       ddcmp_ALeRESPleN},          Run,            {ddcmp_CompleteAckedTransmits, 
                                                                    ddcmp_SetAeqRESP, 
                                                                    ddcmp_SetTequalAplus1, 
                                                                    ddcmp_StopTimer}},
    {33, Run,         {ddcmp_ReceiveNak,
                       ddcmp_RESPleAOrRESPgtN},    Run,            {ddcmp_Ignore}},
    {34, Run,         {ddcmp_TimerExpired},        Run,            {ddcmp_SetSREP}},
    {35, Run,         {ddcmp_TransmitterIdle,
                       ddcmp_SNAKisSet},           Run,            {ddcmp_SendNak, 
                                                                    ddcmp_ClearSNAK}},
    {36, Run,         {ddcmp_TransmitterIdle,
                       ddcmp_SNAKisClear,
                       ddcmp_SREPisSet},           Run,            {ddcmp_SendRep, 
                                                                    ddcmp_ClearSREP}},
    {37, Run,         {ddcmp_TransmitterIdle,
                       ddcmp_SNAKisClear,
                       ddcmp_SREPisClear,
                       ddcmp_TltNplus1},           Run,            {ddcmp_ReTransmitMessageT, 
                                                                    ddcmp_IncrementT, 
                                                                    ddcmp_ClearSACK}},
    {38, Run,         {ddcmp_UserSendMessage,
                       ddcmp_TeqNplus1,
                       ddcmp_TransmitterIdle,
                       ddcmp_SNAKisClear,
                       ddcmp_SREPisClear},         Run,            {ddcmp_SendDataMessage, 
                                                                    ddcmp_ClearSACK}},
    {39, Run,         {ddcmp_TransmitterIdle,
                       ddcmp_SNAKisClear,
                       ddcmp_SREPisClear,
                       ddcmp_SACKisSet,
                       ddcmp_TeqNplus1},           Run,            {ddcmp_SendAck, 
                                                                    ddcmp_ClearSACK}},
    {40, Run,         {ddcmp_DataMessageSent},     Run,            {ddcmp_SetXSetNUM, 
                                                                    ddcmp_IfAltXthenStartTimer, 
                                                                    ddcmp_IfAgeXthenStopTimer}},
    {41, Run,         {ddcmp_REPMessageSent},      Run,            {ddcmp_StartTimer}},
    {42, Maintenance, {ddcmp_ReceiveMaintMsg},     Maintenance,    {ddcmp_GiveBufferToUser}},
    {43, Maintenance, {ddcmp_UserSendMessage,
                       ddcmp_LineConnected,
                       ddcmp_TransmitterIdle},     Maintenance,    {ddcmp_SendMaintMessage}},
    {44, All}           /* End of Table */
    };

typedef struct _ddcmp_condition_name {
    DDCMP_Condition_Routine Condition;
    const char *Name;
    } DDCMP_CONDITION_NAME;

#define NAME(name) {ddcmp_##name,#name}
DDCMP_CONDITION_NAME ddcmp_Condition_Names[] = {
    NAME(UserHalt),
    NAME(UserStartup),
    NAME(UserMaintenanceMode),
    NAME(ReceiveStack),
    NAME(ReceiveStrt),
    NAME(TimerRunning),
    NAME(TimerNotRunning),
    NAME(TimerExpired),
    NAME(ReceiveMaintMessage),
    NAME(ReceiveAck),
    NAME(ReceiveNak),
    NAME(ReceiveRep),
    NAME(NUMEqRplus1),
    NAME(NUMGtRplus1),
    NAME(ReceiveDataMsg),
    NAME(ReceiveMaintMsg),
    NAME(ALtRESPleN),
    NAME(ALeRESPleN),
    NAME(RESPleAOrRESPgtN),
    NAME(TltNplus1),
    NAME(TeqNplus1),
    NAME(ReceiveMessageError),
    NAME(NumEqR),
    NAME(NumNeR),
    NAME(TransmitterIdle),
    NAME(TramsmitterBusy),
    NAME(SACKisSet),
    NAME(SACKisClear),
    NAME(SNAKisSet),
    NAME(SNAKisClear),
    NAME(SREPisSet),
    NAME(SREPisClear),
    NAME(UserSendMessage),
    NAME(LineConnected),
    NAME(LineDisconnected),
    NAME(DataMessageSent),
    NAME(REPMessageSent),
    {NULL, NULL}
    };

char *ddcmp_conditions(DDCMP_Condition_Routine *Conditions)
{
static char buf[512];
DDCMP_CONDITION_NAME *Name;

buf[0] = '\0';
while (*Conditions)
    {
    for (Name = ddcmp_Condition_Names; Name->Condition != NULL; ++Name) {
        if (Name->Condition == *Conditions)
            break;
        }
    ++Conditions;
    if (Name->Name)
        sprintf (&buf[strlen(buf)], "%s%s", Name->Name, *Conditions ? " && " : "");
    }
return buf;
}

typedef struct _ddcmp_action_name {
    DDCMP_LinkAction_Routine Action;
    const char *Name;
    } DDCMP_ACTION_NAME;

DDCMP_ACTION_NAME ddcmp_Actions[] = {
    NAME(StartTimer),
    NAME(StopTimer),
    NAME(ResetVariables),
    NAME(SendStrt),
    NAME(SendStack),
    NAME(SendAck),
    NAME(SendNak),
    NAME(SendRep),
    NAME(SetSACK),
    NAME(ClearSACK),
    NAME(SetSNAK),
    NAME(ClearSNAK),
    NAME(SetSREP),
    NAME(ClearSREP),
    NAME(IncrementR),
    NAME(SetAeqRESP),
    NAME(SetTequalAplus1),
    NAME(IncrementT),
    NAME(SetNAKreason3),
    NAME(SetNAKReasonCRCError),
    NAME(NAKMissingPacket),
    NAME(IfTleAthenSetTeqAplus1),
    NAME(IfAltXthenStartTimer),
    NAME(IfAgeXthenStopTimer),
    NAME(Ignore),
    NAME(GiveBufferToUser),
    NAME(CompleteAckedTransmits),
    NAME(ReTransmitMessageT),
    NAME(NotifyDisconnect),
    NAME(NotifyStartRcvd),
    NAME(NotifyMaintRcvd),
    NAME(SendDataMessage),
    NAME(SendMaintMessage),
    NAME(SetXSetNUM),
    {NULL, NULL}
    };

char *ddcmp_actions(DDCMP_LinkAction_Routine *Actions)
{
static char buf[512];
DDCMP_ACTION_NAME *Name;

buf[0] = '\0';
while (*Actions)
    {
    for (Name = ddcmp_Actions; Name->Action != NULL; ++Name) {
        if (Name->Action == *Actions)
            break;
        }
    ++Actions;
    if (Name->Name)
        sprintf (&buf[strlen(buf)], "%s%s", Name->Name, *Actions ? " + " : "");
    }
return buf;
}

char *ddcmp_link_state(DDCMP *link)
{
static char buf[512];

sprintf (buf, "(R:%d,N:%d,A:%d,T:%d,X:%d,SACK:%d,SNAK:%d,SREP:%d,NAKed:%d)", link->R, link->N, link->A, link->T, link->X, link->SACK, link->SNAK, link->SREP, link->NAKed);
return buf;
}

char *controller_queue_state(CTLR *controller)
{
static char buf[512];

sprintf (buf, "(ACKW:%d,XMT:%d,RCV:%d,CMPL:%d,FREE:%d) TOT:%d", 
                    (int)controller->ack_wait_queue->count, 
                    (int)controller->xmt_queue->count, 
                    (int)controller->rcv_queue->count, 
                    (int)controller->completion_queue->count, 
                    (int)controller->free_queue->count,
                    (int)controller->ack_wait_queue->count+
                    (int)controller->xmt_queue->count+
                    (int)controller->rcv_queue->count+ 
                    (int)controller->completion_queue->count+
                    (int)controller->free_queue->count);
return buf;
}

DDCMP_LinkState NewState;
DDCMP_LinkAction_Routine Actions[10];

#define ctlr up7                        /* Unit back pointer to controller */

void ddcmp_dispatch               (CTLR *controller, uint32 EventMask);


t_stat dmc_rd (int32* data, int32 PA, int32 access);
t_stat dmc_wr (int32  data, int32 PA, int32 access);
t_stat dmc_svc (UNIT * uptr);
t_stat dmc_poll_svc (UNIT * uptr);
t_stat dmc_timer_svc (UNIT * uptr);
t_stat dmc_reset (DEVICE * dptr);
t_stat dmc_attach (UNIT * uptr, CONST char * cptr);
t_stat dmc_detach (UNIT * uptr);
int32 dmc_ininta   (void);
int32 dmc_outinta (void);
t_stat dmc_setnumdevices (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat dmc_shownumdevices (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat dmc_setpeer (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
t_stat dmc_showpeer (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat dmc_setspeed (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
t_stat dmc_showspeed (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat dmc_setcorrupt (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat dmc_showcorrupt (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat dmc_set_microdiag (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
t_stat dmc_show_microdiag (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat dmc_settype (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
t_stat dmc_showtype (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat dmc_setstats (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
t_stat dmc_showstats (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat dmc_showqueues (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat dmc_setconnectpoll (UNIT* uptr, int32 val, CONST char* cptr, void* desc);
t_stat dmc_showconnectpoll (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat dmc_showddcmp (FILE* st, UNIT* uptr, int32 val, CONST void* desc);
t_stat dmc_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
t_stat dmc_help_attach (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
const char *dmc_description (DEVICE *dptr);
const char *dmp_description (DEVICE *dptr);
int dmc_is_attached (UNIT* uptr);
int dmc_is_dmc (CTLR *controller);
int dmc_is_rqi_set (CTLR *controller);
int dmc_is_rdyi_set (CTLR *controller);
int dmc_is_iei_set (CTLR *controller);
int dmc_is_ieo_set (CTLR *controller);
uint32 dmc_get_addr (CTLR *controller);
void dmc_set_addr (CTLR *controller, uint32 addr);
uint16 dmc_get_count (CTLR *controller);
void dmc_set_count (CTLR *controller, uint16 count);
uint8 dmc_get_modem (CTLR *controller);
void dmc_set_modem_dtr (CTLR *controller);
void dmc_clr_modem_dtr (CTLR *controller);
void dmc_process_immediate(CTLR *controller);
void dmc_process_command (CTLR *controller);
t_bool dmc_buffer_fill_receive_buffers (CTLR *controller);
void dmc_start_transfer_buffer (CTLR *controller);
void dmc_buffer_queue_init (CTLR *controller, BUFFER_QUEUE *q, const char *name, size_t size, BUFFER *buffers);
void dmc_buffer_queue_init_all (CTLR *controller);
BUFFER *dmc_buffer_queue_head (BUFFER_QUEUE *q);
BUFFER *dmc_buffer_allocate (CTLR *controller);
t_bool dmc_transmit_queue_empty  (CTLR *controller);
void dmc_ddcmp_start_transmitter (CTLR *controller);
void dmc_queue_control_out (CTLR *controller, uint16 sel6);

/* debugging bitmaps */
#define DBG_TRC  0x0001                                 /* trace routine calls */
#define DBG_REG  0x0002                                 /* trace programatic read/write registers */
#define DBG_RGC  0x0004                                 /* internal read/write registers changes */
#define DBG_WRN  0x0008                                 /* display warnings */
#define DBG_INF  0x0010                                 /* display informational messages (high level trace) */
#define DBG_DTS  (DDCMP_DBG_PXMT|DDCMP_DBG_PRCV)        /* display data summary */
#define DBG_DAT  (DBG_DTS|DDCMP_DBG_PDAT)               /* display data buffer contents */
#define DBG_MDM  0x0040                                 /* modem related transitions */
#define DBG_CON  TMXR_DBG_CON                           /* display socket open/close, connection establishment */
#define DBG_INT  0x0080                                 /* display interrupt activites */


DEBTAB dmc_debug[] = {
    {"TRACE",   DBG_TRC, "trace routine calls"},
    {"WARN",    DBG_WRN, "warnings"},
    {"REG",     DBG_REG, "read/write registers"},
    {"INTREG",  DBG_RGC, "internal read/write registers changes"},
    {"INFO",    DBG_INF, "informational messages (high level trace)"},
    {"DATA",    DBG_DAT, "data buffer contents"},
    {"DATASUM", DBG_DTS, "data summary"},
    {"MODEM",   DBG_MDM, "modem related transitions"},
    {"CONNECT", DBG_CON, "connection establishment"},
    {"INT",     DBG_INT, "interrupt activites"},
    {0}
    };

UNIT dmc_units[DMC_NUMDEVICE+2];            /* One per device plus an I/O polling unit and a timing unit */

UNIT dmc_unit_template = { UDATA (&dmc_svc, UNIT_ATTABLE|UNIT_IDLE, 0) };
UNIT dmc_poll_unit_template = { UDATA (&dmc_poll_svc, UNIT_DIS|UNIT_IDLE, 0) };
UNIT dmc_timer_unit_template = { UDATA (&dmc_timer_svc, UNIT_DIS|UNIT_IDLE, 0) };

UNIT dmp_units[DMP_NUMDEVICE+2];            /* One per device plus an I/O polling unit and a timing unit */

CSRS dmc_csrs[DMC_NUMDEVICE];
uint16 dmc_sel0[DMC_NUMDEVICE];
uint16 dmc_sel2[DMC_NUMDEVICE];
uint16 dmc_sel4[DMC_NUMDEVICE];
uint16 dmc_sel6[DMC_NUMDEVICE];

#define PEERSIZE 512
uint32 dmc_speed[DMC_NUMDEVICE];
char dmc_peer[DMC_NUMDEVICE][PEERSIZE];
char dmc_port[DMC_NUMDEVICE][PEERSIZE];
uint32 dmc_baseaddr[DMC_NUMDEVICE];
uint16 dmc_basesize[DMC_NUMDEVICE];
uint8 dmc_modem[DMC_NUMDEVICE];
t_bool dmc_microdiag[DMC_NUMDEVICE];
int32 dmc_corruption[DMC_NUMDEVICE];

CSRS dmp_csrs[DMP_NUMDEVICE];
uint16 dmp_sel0[DMC_NUMDEVICE];
uint16 dmp_sel2[DMC_NUMDEVICE];
uint16 dmp_sel4[DMC_NUMDEVICE];
uint16 dmp_sel6[DMC_NUMDEVICE];
uint16 dmp_sel10[DMC_NUMDEVICE];

uint32 dmp_speed[DMP_NUMDEVICE];
char dmp_peer[DMP_NUMDEVICE][PEERSIZE];
char dmp_port[DMP_NUMDEVICE][PEERSIZE];
uint32 dmp_baseaddr[DMP_NUMDEVICE];
uint16 dmp_basesize[DMP_NUMDEVICE];
uint8 dmp_modem[DMP_NUMDEVICE];
int32 dmp_corruption[DMC_NUMDEVICE];

TMLN dmc_ldsc[DMC_NUMDEVICE];               /* line descriptors */
TMXR dmc_desc = { 1, NULL, 0, dmc_ldsc };   /* mux descriptor */
uint32 dmc_connect_poll = DMC_CONNECT_POLL; /* seconds between polls when no connection */

uint32 dmc_ini_summary = 0;         /* In Command Interrupt Summary for all controllers */
uint32 dmc_outi_summary = 0;        /* Out Command Interrupt Summary for all controllers */

BUFFER_QUEUE dmc_rcv_queues[DMC_NUMDEVICE];
BUFFER_QUEUE dmc_completion_queues[DMC_NUMDEVICE];
BUFFER_QUEUE dmc_xmt_queues[DMC_NUMDEVICE];
BUFFER_QUEUE dmc_ack_wait_queues[DMC_NUMDEVICE];
BUFFER_QUEUE dmc_free_queues[DMC_NUMDEVICE];
BUFFER *dmc_buffers[DMC_NUMDEVICE];

TMLN dmp_ldsc[DMC_NUMDEVICE];               /* line descriptors */
TMXR dmp_desc = { 1, NULL, 0, dmp_ldsc };   /* mux descriptor */
uint32 dmp_connect_poll;                    /* seconds between polls when no connection */

BUFFER_QUEUE dmp_rcv_queues[DMP_NUMDEVICE];
BUFFER_QUEUE dmp_completion_queues[DMP_NUMDEVICE];
BUFFER_QUEUE dmp_xmt_queues[DMP_NUMDEVICE];
BUFFER_QUEUE dmp_ack_wait_queues[DMP_NUMDEVICE];
BUFFER_QUEUE dmp_free_queues[DMP_NUMDEVICE];
BUFFER *dmp_buffers[DMC_NUMDEVICE];

REG dmc_reg[] = {
    { GRDATAD (RXINT,      dmc_ini_summary, DEV_RDX, 32, 0,                     "input interrupt summary") },
    { GRDATAD (TXINT,     dmc_outi_summary, DEV_RDX, 32, 0,                     "output interrupt summary") },
    { GRDATAD (POLL,      dmc_connect_poll, DEV_RDX, 32, 0,                     "connect poll interval") },
    { BRDATAD (SEL0,              dmc_sel0, DEV_RDX, 16, DMC_NUMDEVICE,         "Select 0 CSR") },
    { BRDATAD (SEL2,              dmc_sel2, DEV_RDX, 16, DMC_NUMDEVICE,         "Select 2 CSR") },
    { BRDATAD (SEL4,              dmc_sel4, DEV_RDX, 16, DMC_NUMDEVICE,         "Select 4 CSR") },
    { BRDATAD (SEL6,              dmc_sel6, DEV_RDX, 16, DMC_NUMDEVICE,         "Select 6 CSR") },
    { BRDATAD (SPEED,            dmc_speed, DEV_RDX, 32, DMC_NUMDEVICE,         "line speed") },
    { BRDATAD (CORRUPT,     dmc_corruption, DEV_RDX, 32, DMC_NUMDEVICE,         "data corruption factor (0.1%)") },
    { BRDATAD (DIAG,         dmc_microdiag, DEV_RDX,  1, DMC_NUMDEVICE,         "Microdiagnostic Enabled") },
    { CRDATAD (PEER,             dmc_peer,  DEV_RDX,  8, DMC_NUMDEVICE*PEERSIZE, "peer address:port") },
    { CRDATAD (PORT,             dmc_port,  DEV_RDX,  8, DMC_NUMDEVICE*PEERSIZE, "listen port") },
    { BRDATAD (BASEADDR,      dmc_baseaddr, DEV_RDX, 32, DMC_NUMDEVICE,         "program set base address") },
    { BRDATAD (BASESIZE,      dmc_basesize, DEV_RDX, 16, DMC_NUMDEVICE,         "program set base size") },
    { BRDATAD (MODEM,            dmc_modem, DEV_RDX,  8, DMC_NUMDEVICE,         "modem control bits") },
    { NULL }  };

REG dmp_reg[] = {
    { GRDATAD (RXINT,      dmc_ini_summary, DEV_RDX, 32, 0,                     "input interrupt summary") },
    { GRDATAD (TXINT,     dmc_outi_summary, DEV_RDX, 32, 0,                     "output interrupt summary") },
    { GRDATAD (POLL,      dmp_connect_poll, DEV_RDX, 32, 0,                     "connect poll interval") },
    { BRDATAD (SEL0,              dmp_sel0, DEV_RDX, 16, DMC_NUMDEVICE,         "Select 0 CSR") },
    { BRDATAD (SEL2,              dmp_sel2, DEV_RDX, 16, DMC_NUMDEVICE,         "Select 2 CSR") },
    { BRDATAD (SEL4,              dmp_sel4, DEV_RDX, 16, DMC_NUMDEVICE,         "Select 4 CSR") },
    { BRDATAD (SEL6,              dmp_sel6, DEV_RDX, 16, DMC_NUMDEVICE,         "Select 6 CSR") },
    { BRDATAD (SEL10,            dmp_sel10, DEV_RDX, 16, DMP_NUMDEVICE,         "Select 10 CSR") },
    { BRDATAD (SPEED,            dmp_speed, DEV_RDX, 32, DMC_NUMDEVICE,         "line speed") },
    { BRDATAD (CORRUPT,     dmp_corruption, DEV_RDX, 32, DMC_NUMDEVICE,         "data corruption factor (0.1%)") },
    { CRDATAD (PEER,              dmp_peer, DEV_RDX,  8, DMC_NUMDEVICE*PEERSIZE, "peer address:port") },
    { CRDATAD (PORT,              dmp_port, DEV_RDX,  8, DMC_NUMDEVICE*PEERSIZE, "listen port") },
    { BRDATAD (BASEADDR,      dmp_baseaddr, DEV_RDX, 32, DMC_NUMDEVICE,         "program set base address") },
    { BRDATAD (BASESIZE,      dmp_basesize, DEV_RDX, 16, DMC_NUMDEVICE,         "program set base size") },
    { BRDATAD (MODEM,            dmp_modem, DEV_RDX,  8, DMP_NUMDEVICE,         "modem control bits") },
    { NULL }  };

extern DEVICE dmc_dev;

MTAB dmc_mod[] = {
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "LINES", "LINES=n",
        &dmc_setnumdevices, &dmc_shownumdevices, (void *) &dmc_dev, "Display number of devices" },
    { MTAB_XTD|MTAB_VUN,          0, "PEER", "PEER=address:port",
        &dmc_setpeer, &dmc_showpeer, NULL, "Display destination/source" },
    { MTAB_XTD|MTAB_VUN,          0, "SPEED", "SPEED=bits/sec (0=unrestricted)" ,
        &dmc_setspeed, &dmc_showspeed, NULL, "Display rate limit" },
    { MTAB_XTD|MTAB_VUN,          0, "MICRODIAG", "MICRODIAG={ENABLE,DISABLE}" ,
        &dmc_set_microdiag, &dmc_show_microdiag, NULL, "MicroDiagnostic Enable" },
#if !defined (VM_PDP10)
    { MTAB_XTD|MTAB_VUN|MTAB_VALR,0, "TYPE", "TYPE={DMR,DMC}" ,&dmc_settype, &dmc_showtype, NULL, "Set/Display device type"  },
#endif
    { MTAB_XTD|MTAB_VUN|MTAB_NMO, 0, "STATS", "STATS",
        &dmc_setstats, &dmc_showstats, NULL, "Display/Clear statistics" },
    { MTAB_XTD|MTAB_VUN|MTAB_NMO, 0, "QUEUES", "QUEUES",
        NULL, &dmc_showqueues, NULL, "Display Queue state" },
    { MTAB_XTD|MTAB_VUN|MTAB_NMO, 0, "DDCMP", "DDCMP",
        NULL, &dmc_showddcmp, NULL, "Display DDCMP state information" },
    { MTAB_XTD|MTAB_VDV,          0, "CONNECTPOLL", "CONNECTPOLL=seconds",
        &dmc_setconnectpoll, &dmc_showconnectpoll, (void *) &dmc_connect_poll, "Display connection poll interval" },
    { MTAB_XTD|MTAB_VUN|MTAB_VALR|MTAB_NMO, 0, "CORRUPT", "CORRUPTION=factor (0=uncorrupted)" ,
      &dmc_setcorrupt, &dmc_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,  NULL, "Interrupt vector" },
    { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "SYNC", NULL,
      NULL, &tmxr_show_sync, NULL, "Display attachable DDCMP synchronous links" },
    { 0 } };

extern DEVICE dmp_dev;

MTAB dmp_mod[] = {
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "LINES", "LINES=n",
        &dmc_setnumdevices, &dmc_shownumdevices, (void *) &dmp_dev, "Display number of devices" },
    { MTAB_XTD|MTAB_VUN,          0, "PEER", "PEER=address:port",
        &dmc_setpeer, &dmc_showpeer, NULL, "Display destination/source" },
    { MTAB_XTD|MTAB_VUN,          0, "SPEED", "SPEED=bits/sec (0=unrestricted)" ,
        &dmc_setspeed, &dmc_showspeed, NULL, "Display rate limit" },
    { MTAB_XTD|MTAB_VUN|MTAB_NMO, 0, "STATS", "STATS",
        &dmc_setstats, &dmc_showstats, NULL, "Display/Clear statistics" },
    { MTAB_XTD|MTAB_VUN|MTAB_NMO, 0, "QUEUES", "QUEUES",
        NULL, &dmc_showqueues, NULL, "Display Queue state" },
    { MTAB_XTD|MTAB_VUN|MTAB_NMO, 0, "DDCMP", "DDCMP",
        NULL, &dmc_showddcmp, NULL, "Display DDCMP state information" },
    { MTAB_XTD|MTAB_VDV,          0, "CONNECTPOLL", "CONNECTPOLL=seconds",
        &dmc_setconnectpoll, &dmc_showconnectpoll, (void *) &dmp_connect_poll, "Display connection poll interval" },
    { MTAB_XTD|MTAB_VUN|MTAB_VALR|MTAB_NMO, 0, "CORRUPTION", "CORRUPTION=factor (0=uncorrupted)" ,
      &dmc_setcorrupt, &dmc_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,  NULL, "Interrupt vector" },
    { 0 } };

extern DEVICE dmv_dev;

MTAB dmv_mod[] = {
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "LINES", "LINES=n",
        &dmc_setnumdevices, &dmc_shownumdevices, (void *) &dmv_dev, "Display number of devices" },
    { MTAB_XTD|MTAB_VUN,          0, "PEER", "PEER=address:port",
        &dmc_setpeer, &dmc_showpeer, NULL, "Display destination/source" },
    { MTAB_XTD|MTAB_VUN,          0, "SPEED", "SPEED=bits/sec (0=unrestricted)" ,
        &dmc_setspeed, &dmc_showspeed, NULL, "Display rate limit" },
    { MTAB_XTD|MTAB_VUN|MTAB_NMO, 0, "STATS", "STATS",
        &dmc_setstats, &dmc_showstats, NULL, "Display/Clear statistics" },
    { MTAB_XTD|MTAB_VUN|MTAB_NMO, 0, "QUEUES", "QUEUES",
        NULL, &dmc_showqueues, NULL, "Display Queue state" },
    { MTAB_XTD|MTAB_VUN|MTAB_NMO, 0, "DDCMP", "DDCMP",
        NULL, &dmc_showddcmp, NULL, "Display DDCMP state information" },
    { MTAB_XTD|MTAB_VDV,          0, "CONNECTPOLL", "CONNECTPOLL=seconds",
        &dmc_setconnectpoll, &dmc_showconnectpoll, (void *) &dmp_connect_poll, "Display connection poll interval" },
    { 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,  NULL, "Interrupt vector" },
    { 0 } };

#define IOLN_DMC        010

DIB dmc_dib = { IOBA_AUTO, IOLN_DMC, &dmc_rd, &dmc_wr, 2, IVCL (DMCRX), VEC_AUTO, {&dmc_ininta, &dmc_outinta}, IOLN_DMC };

#define IOLN_DMP        010

DIB dmp_dib = { IOBA_AUTO, IOLN_DMP, &dmc_rd, &dmc_wr, 2, IVCL (DMCRX), VEC_AUTO, {&dmc_ininta, &dmc_outinta }, IOLN_DMP};

#define IOLN_DMV        020

DIB dmv_dib = { IOBA_AUTO, IOLN_DMV, &dmc_rd, &dmc_wr, 2, IVCL (DMCRX), VEC_AUTO, {&dmc_ininta, &dmc_outinta }, IOLN_DMV};

DEVICE dmc_dev =
    { 
#if defined (VM_PDP10)
    "DMR",
#else
    "DMC", 
#endif
    dmc_units, dmc_reg, dmc_mod, 3, DMC_RDX, 8, 1, DMC_RDX, 8,
    NULL, NULL, &dmc_reset, NULL, &dmc_attach, &dmc_detach,
    &dmc_dib, DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG | DEV_DONTAUTO, 0, dmc_debug,
    NULL, NULL, &dmc_help, &dmc_help_attach, NULL, &dmc_description };

/*
   We have two devices defined here (dmp_dev and dmv_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 dmc_reset allows only 
   one of these devices to be enabled at a time.  The DMP device is allowed 
   on Unibus systems and the DMV device Qbus systems.
   This monkey business is necessary due to the fact that although both
   the DMP and DMV 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.
 */
DEVICE dmp_dev =
    { "DMP", dmp_units, dmp_reg, dmp_mod, 3, DMC_RDX, 8, 1, DMC_RDX, 8,
    NULL, NULL, &dmc_reset, NULL, &dmc_attach, &dmc_detach,
    &dmp_dib, DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG | DEV_DONTAUTO, 0, dmc_debug,
    NULL, NULL, &dmc_help, &dmc_help_attach, NULL, &dmp_description };

DEVICE dmv_dev =
    { "DMV", dmp_units, dmp_reg, dmv_mod, 3, DMC_RDX, 8, 1, DMC_RDX, 8,
    NULL, NULL, &dmc_reset, NULL, &dmc_attach, &dmc_detach,
    &dmp_dib, DEV_DISABLE | DEV_DIS | DEV_QBUS | DEV_DEBUG | DEV_DONTAUTO, 0, dmc_debug,
    NULL, NULL, &dmc_help, &dmc_help_attach, NULL, &dmp_description };

CTLR dmc_ctrls[DMC_NUMDEVICE + DMP_NUMDEVICE];

int dmc_is_attached(UNIT* uptr)
{
return uptr->flags & UNIT_ATT;
}

int dmc_is_dmc(CTLR *controller)
{
return controller->dev_type != DMP;
}

CTLR *dmc_get_controller_from_unit(UNIT *unit)
{
return (CTLR *)unit->ctlr;
}

CTLR* dmc_get_controller_from_address(uint32 address)
{
int i;

for (i=0; i<DMC_NUMDEVICE + DMP_NUMDEVICE; i++) {
    DIB *dib = (DIB *)dmc_ctrls[i].device->ctxt;
    if ((address >= dib->ba) && (address < (dib->ba + dib->lnt)))
        return &dmc_ctrls[(address - dib->ba) >> ((UNIBUS) ? 3 : 4)];
    }
/* not found */
return 0;
}

t_stat dmc_showpeer (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
DEVICE *dptr = (UNIBUS) ? ((&dmc_dev == find_dev_from_unit(uptr)) ? &dmc_dev : &dmp_dev) : &dmv_dev;
int32 dmc = (int32)(uptr-dptr->units);
char *peer = ((dptr == &dmc_dev)? &dmc_peer[dmc][0] : &dmp_peer[dmc][0]);

if (peer[0])
    fprintf(st, "peer=%s", peer);
else
    fprintf(st, "peer=unspecified");
return SCPE_OK;
}

t_stat dmc_setpeer (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
DEVICE *dptr = (UNIBUS) ? ((&dmc_dev == find_dev_from_unit(uptr)) ? &dmc_dev : &dmp_dev) : &dmv_dev;
int32 dmc = (int32)(uptr-dptr->units);
char *peer = ((dptr == &dmc_dev)? &dmc_peer[dmc][0] : &dmp_peer[dmc][0]);
char host[PEERSIZE], port[PEERSIZE];

if ((!cptr) || (!*cptr))
    return SCPE_ARG;
if (dmc_is_attached(uptr))
    return SCPE_ALATT;
if (sim_parse_addr (cptr, host, sizeof(host), NULL, port, sizeof(port), NULL, NULL))
    return SCPE_ARG;
if (host[0] == '\0')
    return SCPE_ARG;
strncpy(peer, cptr, PEERSIZE-1);
return SCPE_OK;
}

t_stat dmc_showspeed (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
DEVICE *dptr = (UNIBUS) ? ((&dmc_dev == find_dev_from_unit(uptr)) ? &dmc_dev : &dmp_dev) : &dmv_dev;
int32 dmc = (int32)(uptr-dptr->units);
uint32 *speeds = ((dptr == &dmc_dev)? dmc_speed : dmp_speed);

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


t_stat dmc_setspeed (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
DEVICE *dptr = (UNIBUS) ? ((&dmc_dev == find_dev_from_unit(uptr)) ? &dmc_dev : &dmp_dev) : &dmv_dev;
int32 dmc = (int32)(uptr-dptr->units);
uint32 *speeds = ((dptr == &dmc_dev)? dmc_speed : dmp_speed);
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;
speeds[dmc] = newspeed;
return SCPE_OK;
}

t_stat dmc_show_microdiag (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
int32 dmc = (int32)(uptr-dmc_dev.units);

fprintf(st, "MicroDiag=%s", dmc_microdiag[dmc] ? "enabled" : "disabled");
return SCPE_OK;
}

/* Manage the corruption troll's appetite, in units of milli-gulps.
 *
 * See ddcmp_feedCorruptionTroll for usage.
 */
t_stat dmc_setcorrupt (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
DEVICE *dptr = (UNIBUS) ? ((&dmc_dev == find_dev_from_unit(uptr)) ? &dmc_dev : &dmp_dev) : &dmv_dev;
int32 dmc = (int32)(uptr-dptr->units);
int32 *hunger = (dptr == &dmc_dev) ? &dmc_corruption[dmc] : &dmp_corruption[dmc];
t_stat r;
int32 appetite;

if ((cptr == NULL) || (*cptr == '\0'))
    return SCPE_ARG;

appetite = (int32) get_uint (cptr, 10, 999, &r);
if (r != SCPE_OK)
    return r;

*hunger = appetite;

return SCPE_OK;
}

/* Display the corruption troll's appetite */

t_stat dmc_showcorrupt (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
DEVICE *dptr = (UNIBUS) ? ((&dmc_dev == find_dev_from_unit(uptr)) ? &dmc_dev : &dmp_dev) : &dmv_dev;
int32 dmc = (int32)(uptr-dptr->units);
int32 *hunger = (dptr == &dmc_dev) ? &dmc_corruption[dmc] : &dmp_corruption[dmc];

if (*hunger)
    fprintf(st, "Corruption=%d milligulps (%.1f%% of messages processed)", 
             *hunger, ((double)*hunger)/10.0);
else
    fprintf(st, "No Corruption");

return SCPE_OK;
}

t_stat dmc_set_microdiag(UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
int32 dmc = (int32)(uptr-dmc_dev.units);
char gbuf[CBUFSIZE];

if ((cptr == NULL) || (*cptr == '\0'))
    return SCPE_ARG;
get_glyph (cptr, gbuf, 0);
if (MATCH_CMD (gbuf, "ENABLE") == 0)
    dmc_microdiag[dmc] = TRUE;
else
    if (MATCH_CMD (gbuf, "DISABLE") == 0)
        dmc_microdiag[dmc] = FALSE;
    else
        return SCPE_ARG;
return SCPE_OK;
}

t_stat dmc_showtype (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
CTLR *controller = dmc_get_controller_from_unit(uptr);

switch (controller->dev_type) {
    case DMC:
        fprintf(st, "type=DMC");
        break;
    case DMR:
        fprintf(st, "type=DMR");
        break;
    case DMP:
        fprintf(st, "type=%s", (UNIBUS) ? "DMP" : "DMV");
        break;
    default:
        fprintf(st, "type=???");
        break;
    }
return SCPE_OK;
}

t_stat dmc_settype (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
char gbuf[80];
t_stat status = SCPE_OK;
CTLR *controller = dmc_get_controller_from_unit(uptr);

if ((!cptr) || (!*cptr))
    return SCPE_2FARG;
if (dmc_is_attached(uptr))
    return SCPE_ALATT;
cptr = get_glyph (cptr, gbuf, 0);
if (strcmp (gbuf,"DMC") == 0)
    controller->dev_type = DMC;
else 
    if (strcmp (gbuf,"DMR") == 0)
        controller->dev_type = DMR;
    else
        status = SCPE_ARG;

return status;
}

t_stat dmc_showstats (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
CTLR *controller = dmc_get_controller_from_unit(uptr);
    
fprintf (st, "%s%d\n", controller->device->name, (int)(uptr-controller->device->units));

tmxr_fstats (st, controller->line, -1);

fprintf(st, "Buffers received from the network=%d\n", controller->buffers_received_from_net);
fprintf(st, "Buffers sent to the network=%d\n", controller->buffers_transmitted_to_net);
fprintf(st, "Output transfers completed for receive buffers=%d\n", controller->receive_buffer_output_transfers_completed);
fprintf(st, "Output transfers completed for transmit buffers=%d\n", controller->transmit_buffer_output_transfers_completed);
fprintf(st, "Input transfers completed for receive buffers=%d\n", controller->receive_buffer_input_transfers_completed);
fprintf(st, "Input transfers completed for transmit buffers=%d\n", controller->transmit_buffer_input_transfers_completed);
fprintf(st, "Control Out operations processed=%d\n", controller->control_out_operations_completed);
fprintf(st, "DDCMP control packets received=%d\n", controller->ddcmp_control_packets_received);
fprintf(st, "DDCMP control packets sent=%d\n", controller->ddcmp_control_packets_sent);

return SCPE_OK;
}

void dmc_showqueue (FILE* st, BUFFER_QUEUE *queue, t_bool detail)
{
size_t i;

fprintf (st, "%s Queue:\n", queue->name);
fprintf (st, "    Size: %d\n", (int)queue->size);
fprintf (st, "   Count: %d\n", (int)queue->count);
if (detail) {
    BUFFER *buffer = (BUFFER *)queue->hdr.next;
    size_t same;

    for (i=same=0; i<queue->count; ++i) {
        if ((i == 0) || 
            (0 != memcmp(((char *)buffer)+sizeof(QH), ((char *)(buffer->hdr.prev))+sizeof(QH), sizeof(*buffer)-sizeof(QH)))) {
            if (same > 0)
                fprintf (st, "%s.queue[%d] thru %s.queue[%d] same as above\n", queue->name, (int)(i-same), queue->name, (int)(i-1));
            fprintf (st, "%s.queue[%d] at %p\n", queue->name, (int)i, buffer);
            fprintf (st, "   address:                  0x%08X\n", buffer->address);
            fprintf (st, "   count:                    0x%04X\n", buffer->count);
            fprintf (st, "   actual_bytes_transferred: 0x%04X\n", buffer->actual_bytes_transferred);
            if (buffer->buffer_return_time)
                fprintf (st, "   buffer_return_time:       0x%08X\n", buffer->buffer_return_time);
            if (strcmp(queue->name, "transmit") == 0) {
                uint8 *msg = buffer->transfer_buffer;
                static const char *const flags [4] = { "..", ".Q", "S.", "SQ" };
                static const char *const nak[18] = { "", " (HCRC)", " (DCRC)", " (REPREPLY)", /* 0-3 */
                                                     "", "", "", "",                          /* 4-7 */
                                                     " (NOBUF)", " (RXOVR)", "", "",          /* 8-11 */
                                                     "", "", "", "",                          /* 12-15 */
                                                     " (TOOLONG)", " (HDRFMT)" };             /* 16-17 */
                const char *flag = flags[msg[2]>>6];
                int msg2 = msg[2] & 0x3F;

                switch (msg[0]) {
                    case DDCMP_SOH:   /* Data Message */
                        fprintf (st, "Data Message, Count: %d, Num: %d, Flags: %s, Resp: %d, HDRCRC: %s, DATACRC: %s\n", (msg2 << 8)|msg[1], msg[4], flag, msg[3], 
                                                    (0 == ddcmp_crc16 (0, msg, 8)) ? "OK" : "BAD", (0 == ddcmp_crc16 (0, msg+8, 2+((msg2 << 8)|msg[1]))) ? "OK" : "BAD");
                        break;
                    case DDCMP_ENQ:   /* Control Message */
                        fprintf (st, "Control: Type: %d ", msg[1]);
                        switch (msg[1]) {
                            case DDCMP_CTL_ACK: /* ACK */
                                fprintf (st, "(ACK) ACKSUB: %d, Flags: %s, Resp: %d\n", msg2, flag, msg[3]);
                                break;
                            case DDCMP_CTL_NAK: /* NAK */
                                fprintf (st, "(NAK) Reason: %d%s, Flags: %s, Resp: %d\n", msg2, ((msg2 > 17)? "": nak[msg2]), flag, msg[3]);
                                break;
                            case DDCMP_CTL_REP: /* REP */
                                fprintf (st, "(REP) REPSUB: %d, Num: %d, Flags: %s\n", msg2, msg[4], flag);
                                break;
                            case DDCMP_CTL_STRT: /* STRT */
                                fprintf (st, "(STRT) STRTSUB: %d, Flags: %s\n", msg2, flag);
                                break;
                            case DDCMP_CTL_STACK: /* STACK */
                                fprintf (st, "(STACK) STCKSUB: %d, Flags: %s\n", msg2, flag);
                                break;
                            default: /* Unknown */
                                fprintf (st, "(Unknown=0%o)\n", msg[1]);
                                break;
                            }
                        if (buffer->count != DDCMP_HEADER_SIZE)
                            fprintf (st, "Unexpected Control Message Length: %d expected %d\n", buffer->count, DDCMP_HEADER_SIZE);
                    }
                }
            same = 0;
            }
        else
            ++same;
        buffer = (BUFFER *)buffer->hdr.next;
        }
    if (same > 0)
        fprintf (st, "%s.queue[%d] thru %s.queue[%d] same as above\n", queue->name, (int)(i-same), queue->name, (int)(i-1));
    }
}

t_stat dmc_showqueues (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
CTLR *controller     = dmc_get_controller_from_unit(uptr);
static const char *states[] = {"Uninitialised", "Initialised", "Running", "Halted"};
static const char *tstates[] = {"Idle", "InputTransfer", "OutputTransfer", "OutputControl"};

dmc_showstats (st, uptr, val, desc);
fprintf (st, "State: %s\n", states[controller->state]);
fprintf (st, "TransferState: %s\n", tstates[controller->transfer_state]);
dmc_showqueue (st, controller->completion_queue, TRUE);
dmc_showqueue (st, controller->xmt_queue, TRUE);
dmc_showqueue (st, controller->ack_wait_queue, TRUE);
dmc_showqueue (st, controller->rcv_queue, TRUE);
dmc_showqueue (st, controller->free_queue, TRUE);
if (controller->control_out) {
    CONTROL_OUT *control;

    fprintf (st, "Control Out Queue:\n");
    for (control=controller->control_out; control; control=control->next)
        fprintf (st, "   SEL6:  0x%04X\n", control->sel6);
    }
return SCPE_OK;
}

t_stat dmc_setstats (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
CTLR *controller = dmc_get_controller_from_unit(uptr);

controller->receive_buffer_output_transfers_completed = 0;
controller->transmit_buffer_output_transfers_completed = 0;
controller->receive_buffer_input_transfers_completed = 0;
controller->transmit_buffer_input_transfers_completed = 0;
controller->control_out_operations_completed = 0;
controller->ddcmp_control_packets_received = 0;
controller->ddcmp_control_packets_sent = 0;

sim_printf("Statistics reset\n");

return SCPE_OK;
}

t_stat dmc_showconnectpoll (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
uint32 poll_interval = *((const uint32 *)desc);

fprintf(st, "connectpoll=%u", poll_interval);
return SCPE_OK;
}

t_stat dmc_setconnectpoll (UNIT* uptr, int32 val, CONST char* cptr, void* desc)
{
t_stat status = SCPE_OK;
uint32 *poll_interval = ((uint32 *)desc);
uint32 newpoll;

if (!cptr) 
    return SCPE_IERR;
newpoll = (int32) get_uint (cptr, 10, 1800, &status);
if ((status != SCPE_OK) || (newpoll == (*poll_interval)))
    return status;
*poll_interval = newpoll;
return tmxr_connection_poll_interval ((poll_interval == &dmc_connect_poll) ? &dmc_desc : &dmp_desc, newpoll);
}

/* SET LINES processor */

t_stat dmc_setnumdevices (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
int32 newln;
uint32 i, j;
t_stat r;
DEVICE *dptr = (DEVICE *)desc;
TMXR *mp = (dptr == &dmc_dev) ? &dmc_desc : &dmp_desc;
int maxunits = (&dmc_dev == dptr) ? DMC_NUMDEVICE : DMP_NUMDEVICE;
DIB *dibptr = (DIB *)dptr->ctxt;
int addrlnt = (UNIBUS) ? IOLN_DMC : IOLN_DMV;

for (j=0; j<2; j++) {
    dptr = (j == 0) ? &dmc_dev : &dmp_dev;
    for (i=0; i<dptr->numunits; i++)
        if (dptr->units[i].flags&UNIT_ATT)
            return SCPE_ALATT;
    }
dptr = (DEVICE *)desc;
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, maxunits, &r);
if ((r != SCPE_OK) || (newln == (int32)(dptr->numunits - 2)))
    return r;
if (newln == 0)
    return SCPE_ARG;
sim_cancel (dptr->units + dptr->numunits - 2);
sim_cancel (dptr->units + dptr->numunits - 1);
for (i=dptr->numunits-2; i<(uint32)newln; ++i) {
    dptr->units[i] = dmc_unit_template;
    dptr->units[i].ctlr = &dmc_ctrls[(dptr == &dmc_dev) ? i : i+DMC_NUMDEVICE];
    }
dibptr->lnt = newln * addrlnt;                      /* set length */
dptr->numunits = newln + 2;
dptr->units[newln] = dmc_poll_unit_template;
dptr->units[newln].ctlr = &dmc_ctrls[(dptr == &dmc_dev) ? 0 : DMC_NUMDEVICE];
dptr->units[newln+1] = dmc_timer_unit_template;
dptr->units[newln+1].ctlr = &dmc_ctrls[(dptr == &dmc_dev) ? 0 : DMC_NUMDEVICE];
mp->lines = newln;
mp->uptr = dptr->units + newln;                     /* Identify polling unit */
return dmc_reset ((DEVICE *)desc);                  /* setup devices and auto config */
}

t_stat dmc_shownumdevices (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
DEVICE *dptr = (UNIBUS) ? find_dev_from_unit (uptr) : &dmv_dev;

fprintf (st, "lines=%d", dptr->numunits-2);
return SCPE_OK;
}

t_stat dmc_showddcmp (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
CTLR *controller = dmc_get_controller_from_unit(uptr);
static const char *states[] = {"Halt", "IStart", "AStart", "Run", "Maintenance"};

fprintf (st, "%s%d\n", controller->device->name, (int)(uptr-controller->device->units));

fprintf(st, "DDCMP Link State: %s\n", states[controller->link.state]);
fprintf(st, "               R: %d\n", controller->link.R);
fprintf(st, "               N: %d\n", controller->link.N);
fprintf(st, "               A: %d\n", controller->link.A);
fprintf(st, "               T: %d\n", controller->link.T);
fprintf(st, "               X: %d\n", controller->link.X);
fprintf(st, "            SACK: %s\n", controller->link.SACK ? "true" : "false");
fprintf(st, "            SNAK: %s\n", controller->link.SNAK ? "true" : "false");
fprintf(st, "            SREP: %s\n", controller->link.SREP ? "true" : "false");
fprintf(st, "         rcv_pkt: %p\n", controller->link.rcv_pkt);
fprintf(st, "    rcv_pkt_size: %d\n", controller->link.rcv_pkt_size);
fprintf(st, "      xmt_buffer: %p\n", controller->link.xmt_buffer);
fprintf(st, "      nak_reason: %d\n", controller->link.nak_reason);
fprintf(st, "    TimerRunning: %s\n", controller->link.TimerRunning ? "true" : "false");
fprintf(st, "   TimeRemaining: %d\n", controller->link.TimeRemaining);
return SCPE_OK;
}

t_stat dmc_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 %1s is a communication subsystem which consists of a microprocessor\n"
    " based, intelligent synchronous communications controller which employs\n"
    " the DIGITAL Data Communications Message Protocol (DDCMP).\n"
    "1 Hardware Description\n"
    " The %1s consists of a microprocessor module and a synchronous line unit\n"
    " module.\n"
#if !defined (VM_PDP10)
    "2 Models\n"
    " There were a number of microprocessor DDCMP devices produced.\n"
    "3 DMC11\n"
    " The original kmc11 microprocessor board with DMC microcode and a sync\n"
    " line unit.\n"
    "3 DMR11\n"
    " The more advanced kmc11 microprocessor board with DMR microcode and a sync\n"
    " line unit.\n"
    "3 DMP11\n"
    " A newly designed Unibus board with a more complete programming interface\n"
    " and a sync line unit.\n"
    "3 DMV11\n"
    " A Qbus version of the DMP11 with some more advanced refinements and a sync\n"
    " line unit.\n"
#endif
    "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 %1s devices can be emulated concurrently in the %S\n"
    " simulator. The number of simulated %1s 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"
#if !defined (VM_PDP10)
    "3 Type\n"
    " The type of device being emulated can be changed with the following\n"
    " command:\n"
    "\n"
    "+sim> SET %U TYPE={DMR|DMC}\n"
    "\n"
    " A SET TYPE command should be entered before the device is attached to a\n"
    " listening port.\n"
#endif
    "3 Corruption\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"
     /****************************************************************************/
#define DMC_HLP_ATTACH "Configuration Attach"
    "2 Attach\n"
    " The device must be attached to a receive port, use the ATTACH command\n"
    " specifying the receive port number.\n"
    "\n"
    "+sim> ATTACH %U port\n"
    "\n"
    " The Peer host:port value must be specified before the attach command.\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"
    "+sim> ATTACH %U port,UDP\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"
    " In SYNC mode, the \"PEER\" parameter is not used and need not be set.\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> SET %U PEER=LOCALHOST:2222\n"
    "+sim> ATTACH %U 1111\n"
    " \n"
    " Machine 2\n"
    "+sim> SET %D ENABLE\n"
    "+sim> SET %U PEER=LOCALHOST:1111\n"
    "+sim> ATTACH %U 2222\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\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"
    " In real hardware, the DMC11 spoke a version of DDCMP which peer devices\n"
    " needed to be aware of.  The DMR11, DMP11, and DMV11 boards have\n"
    " configuration switches or programatic methods to indicate that the peer\n"
    " device was a DMC11.  The emulated devices all speak the same level of\n"
    " DDCMP so no special remote device awareness need be considered.\n"
    "1 Implementation\n"
    " A real %1s transports data using DDCMP via a synchronous connection, the\n"
    " emulated device makes a TCP/IP connection to another emulated device which\n"
    " either speaks DDCMP over the TCP connection directly, or interfaces to a\n"
    " simulated computer where the operating system speaks the DDCMP protocol on\n"
    " the wire.\n"
    "\n"
    " The %1s can be used for point-to-point DDCMP connections carrying\n"
    " DECnet 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"
    "++INTREG  Shows internal register value changes.\n"
    "++INFO    Shows higher-level tracing only.\n"
    "++WARN    Shows any warnings.\n"
    "++TRACE   Shows more detailed trace information.\n"
    "++DATASUM Brief summary of each received and transmitted buffer.\n"
    "++++Ignored if DATA is set.\n"
    "++DATA    Shows the actual data sent and received.\n"
    "++MODEM   Shows modem signal transitions details.\n"
    "++CONNECT Shows sockets connection activity.\n"
    "++INT     Shows Interrupt activity.\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;INFO;WARN\n"
    "\n"
    "1 Related Devices\n"
    " The %D 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 devname[16];
char devcount[16];
char connectpoll[16];

sprintf (devname, "%s11" , dptr->name);
sprintf (devcount, "%d", (dptr == &dmc_dev) ? DMC_NUMDEVICE : DMP_NUMDEVICE);
sprintf (connectpoll, "%d", DMC_CONNECT_POLL);

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

t_stat dmc_help_attach (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
return dmc_help (st, dptr, uptr, flag, DMC_HLP_ATTACH);
}

void dmc_setinint(CTLR *controller)
{
if (!dmc_is_iei_set(controller))
    return;
if (!controller->in_int) {
    sim_debug(DBG_INT, controller->device, "SET_INT(RX:%s%d) summary=0x%x\n", controller->device->name, controller->index, dmc_ini_summary);
    }
controller->in_int = 1;
dmc_ini_summary |= (1u << controller->index);
SET_INT(DMCRX);
}

void dmc_clrinint(CTLR *controller)
{
controller->in_int = 0;
if (dmc_ini_summary & (1u << controller->index)) {
    sim_debug(DBG_INT, controller->device, "CLR_INT(RX:%s%d) summary=0x%x\n", controller->device->name, controller->index, dmc_ini_summary);
    }
dmc_ini_summary &= ~(1u << controller->index);
if (!dmc_ini_summary)
    CLR_INT(DMCRX);
else
    SET_INT(DMCRX);
}

void dmc_setoutint(CTLR *controller)
{
if (!dmc_is_ieo_set(controller))
    return;
if (!controller->out_int) {
    sim_debug(DBG_INT, controller->device, "SET_INT(TX:%s%d) summary=0x%x\n", controller->device->name, controller->index, dmc_outi_summary );
    }
controller->out_int = 1;
dmc_outi_summary |= (1u << controller->index);
SET_INT(DMCTX);
}

void dmc_clroutint(CTLR *controller)
{
controller->out_int = 0;
if (dmc_outi_summary & (1u << controller->index)) {
    sim_debug(DBG_INT, controller->device, "CLR_INT(TX:%s%d) summary=0x%x\n", controller->device->name, controller->index, dmc_outi_summary);
    }
dmc_outi_summary &= ~(1u << controller->index);
if (!dmc_outi_summary)
    CLR_INT(DMCTX);
else
    SET_INT(DMCTX);
}

int dmc_getsel(int addr)
{
return (addr >> 1) & ((UNIBUS) ? 03 : 07);
}

uint16 dmc_bitfld(int data, int start_bit, int length)
{
uint16 ans = (uint16)(data >> start_bit);
uint32 mask = (1 << (length))-1;
ans &= mask;
return ans;
}

void dmc_dumpregsel0(CTLR *controller, int trace_level, const char * prefix, uint16 data)
{
const char *type_str = "";
uint16 type = dmc_bitfld(data, DMC_SEL0_V_ITYPE, DMC_SEL0_S_ITYPE);
static const char *dmc_types[] = {"XMT BA/CC", "CNTL IN", "HALT", "BASE IN", "RCV BA/CC", "?????",  "?????", "?????"};

if (dmc_is_dmc(controller)) {
    if (dmc_is_rqi_set(controller)) {
        type_str = dmc_types[type];
        }

    sim_debug(
        trace_level,
        controller->device,
        "%s%d: %s SEL0 (0x%04x) %s%s%s%s%s%s%s%s%s\n",
        controller->device->name, controller->index,
        prefix,
        data,
        dmc_bitfld(data, DMC_SEL0_V_RUN, 1) ? "RUN " : "",
        dmc_bitfld(data, DMC_SEL0_V_MCLEAR, 1) ? "MCLR " : "",
        dmc_bitfld(data, DMC_SEL0_V_LU_LOOP, 1) ? "LU LOOP " : "",
        dmc_bitfld(data, DMC_SEL0_V_ROMI, 1) ? "ROMI " : "",
        dmc_bitfld(data, DMC_SEL0_V_STEPUP, 1) ? "STEPUP " : "",
        dmc_bitfld(data, DMC_SEL0_V_RDI, 1) ? "RDI " : "",
        dmc_bitfld(data, DMC_SEL0_V_IEI, 1) ? "IEI " : "",
        dmc_bitfld(data, DMC_SEL0_V_RQI, 1) ? "RQI " : "",
        type_str
        );
    }
else {
    sim_debug(
        trace_level,
        controller->device,
        "%s%d: %s SEL0 (0x%04x) %s%s%s%s%s%s\n",
        controller->device->name, controller->index,
        prefix,
        data,
        dmc_bitfld(data, DMC_SEL0_V_RUN, 1) ? "RUN " : "",
        dmc_bitfld(data, DMC_SEL0_V_MCLEAR, 1) ? "MCLR " : "",
        dmc_bitfld(data, DMC_SEL0_V_LU_LOOP, 1) ? "LU LOOP " : "",
        dmc_bitfld(data, DMP_SEL0_V_RQI, 1) ? "RQI " : "",
        dmc_bitfld(data, DMP_SEL0_V_IEO, 1) ? "IEO " : "",
        dmc_bitfld(data, DMP_SEL0_V_IEI, 1) ? "IEI " : ""
        );
    }
}

void dmc_dumpregsel2(CTLR *controller, int trace_level, const char *prefix, uint16 data)
{
const char *type_str = "";
uint16 type = dmc_bitfld(data, DMC_SEL2_V_CODE, DMC_SEL2_S_CODE);
static const char *dmc_types[] = {"XMT BA/CC OUT", "CNTL OUT", "?????", "?????", "RCV BA/CC OUT", "?????",  "?????", "?????"};

type_str = dmc_types[type];

sim_debug(
    trace_level,
    controller->device,
    "%s%d: %s SEL2 (0x%04x) PRIO=%d LINE=%d %s%s%s%s\n",
    controller->device->name, controller->index,
    prefix,
    data,
    dmc_bitfld(data, SEL2_PRIO_BIT, SEL2_PRIO_BIT_LENGTH),
    dmc_bitfld(data, SEL2_LINE_BIT, SEL2_LINE_BIT_LENGTH),
    dmc_bitfld(data, SEL2_RDO_BIT, 1) ? "RDO " : "",
    dmc_bitfld(data, SEL2_IEO_BIT, 1) ? "IEO " : "",
    dmc_bitfld(data, SEL2_OUT_IO_BIT, 1) ? "OUT I/O " : "",
    type_str
    );
}

void dmc_dumpregsel4(CTLR *controller, int trace_level, const char *prefix, uint16 data)
{
if (dmc_is_rdyi_set(controller)) {
    sim_debug(
        trace_level,
        controller->device,
        "%s%d: %s SEL4 (0x%04x) %s%s%s%s%s%s%s%s\n",
        controller->device->name, controller->index,
        prefix,
        data,
        dmc_bitfld(data, DMC_SEL4_V_RI, 1) ? "RI " : "",
        dmc_bitfld(data, DMC_SEL4_V_DTR, 1) ? "DTR " : "",
        dmc_bitfld(data, DMC_SEL4_V_RTS, 1) ? "RTS " : "",
        dmc_bitfld(data, DMC_SEL4_V_HDX, 1) ? "HDX " : "",
        dmc_bitfld(data, DMC_SEL4_V_DSR, 1) ? "DSR " : "",
        dmc_bitfld(data, DMC_SEL4_V_CTS, 1) ? "CTS " : "",
        dmc_bitfld(data, DMC_SEL4_V_STN, 1) ? "STN " : "",
        dmc_bitfld(data, DMC_SEL4_V_CAR, 1) ? "CAR " : "");
    }
else {
    sim_debug(
        trace_level,
        controller->device,
        "%s%d: %s SEL4 (0x%04x)\n",
        controller->device->name, controller->index,
        prefix,
        data);
    }
}

void dmc_dumpregsel6(CTLR *controller, int trace_level, const char *prefix, uint16 data)
{
if (dmc_is_rdyi_set(controller)) {
    sim_debug(
        trace_level,
        controller->device,
        "%s%d: %s SEL6 (0x%04x) %s%s\n",
        controller->device->name, controller->index,
        prefix,
        data,
        dmc_bitfld(data, DMC_SEL6_M_LOSTDATA, 1) ? "LOST_DATA " : "",
        dmc_bitfld(data, DMC_SEL6_M_DISCONN, 1) ? "DISCONNECT " : "");
    }
else {
    sim_debug(
        trace_level,
        controller->device,
        "%s%d: %s SEL6 (0x%04x)\n",
        controller->device->name, controller->index,
        prefix,
        data);
    }
}

void dmc_dumpregsel10(CTLR *controller, int trace_level, const char *prefix, uint16 data)
{
sim_debug(
    trace_level,
    controller->device,
    "%s%d: %s SEL10 (0x%04x) %s\n",
    controller->device->name, controller->index,
    prefix,
    data,
    dmc_bitfld(data, DMC_SEL6_M_LOSTDATA, 1) ? "LOST_DATA " : "");
}

uint16 dmc_getreg(CTLR *controller, int reg, int ctx)
{
uint16 ans = 0;

switch (dmc_getsel(reg)) {
    case 00:
        ans = *controller->csrs->sel0;
        dmc_dumpregsel0(controller, ctx, "Getting", ans);
        break;
    case 01:
        ans = *controller->csrs->sel2;
        dmc_dumpregsel2(controller, ctx, "Getting", ans);
        break;
    case 02:
        ans = *controller->csrs->sel4;
        dmc_dumpregsel4(controller, ctx, "Getting", ans);
        break;
    case 03:
        ans = *controller->csrs->sel6;
        dmc_dumpregsel6(controller, ctx, "Getting", ans);
        break;
    case 04:
        ans = *controller->csrs->sel10;
        dmc_dumpregsel10(controller, ctx, "Getting", ans);
        break;
    default:
        sim_debug(DBG_WRN, controller->device, "%s%d: dmc_getreg(). Invalid register %d", controller->device->name, controller->index, reg);
        break;
    }

return ans;
}

void dmc_setreg(CTLR *controller, int reg, uint16 data, int ctx)
{
const char *trace = "Setting";

switch (dmc_getsel(reg)) {
    case 00:
        dmc_dumpregsel0(controller, ctx, trace, data);
        *controller->csrs->sel0 = data;
        break;
    case 01:
        dmc_dumpregsel2(controller, ctx, trace, data);
        *controller->csrs->sel2 = data;
        break;
    case 02:
        dmc_dumpregsel4(controller, ctx, trace, data);
        *controller->csrs->sel4 = data;
        break;
    case 03:
        dmc_dumpregsel6(controller, ctx, trace, data);
        *controller->csrs->sel6 = data;
        break;
    case 04:
        dmc_dumpregsel10(controller, ctx, trace, data);
        *controller->csrs->sel10 = data;
        break;
    default:
        sim_debug(DBG_WRN, controller->device, "%s%d: dmc_setreg(). Invalid register %d", controller->device->name, controller->index, reg);
    }
}

int dmc_is_master_clear_set(CTLR *controller)
{
return *controller->csrs->sel0 & DMC_SEL0_M_MCLEAR;
}

int dmc_is_lu_loop_set(CTLR *controller)
{
if (dmc_is_dmc(controller))
    return ((*controller->csrs->sel0 & DMC_SEL0_M_LU_LOOP) != 0);
else
    return FALSE;
}

int dmc_is_run_set(CTLR *controller)
{
return *controller->csrs->sel0 & DMC_SEL0_M_RUN;
}

int dmc_is_rqi_set(CTLR *controller)
{
int ans = 0;

if (dmc_is_dmc(controller))
    ans = *controller->csrs->sel0 & DMC_SEL0_M_RQI;
else
    ans = *controller->csrs->sel0 & DMP_SEL0_M_RQI;
return ans;
}

int dmc_is_rdyi_set(CTLR *controller)
{
int ans = 0;

if (dmc_is_dmc(controller))
    ans = *controller->csrs->sel0 & DMC_SEL0_M_RDI;
else
    ans = *controller->csrs->sel2 & DMP_SEL2_M_RDI;
return ans;
}

int dmc_is_iei_set(CTLR *controller)
{
int ans = 0;

if (dmc_is_dmc(controller))
    ans = *controller->csrs->sel0 & DMC_SEL0_M_IEI;
else
    ans = *controller->csrs->sel0 & DMP_SEL0_M_IEI;
return ans;
}

int dmc_is_ieo_set(CTLR *controller)
{
int ans = 0;

if (dmc_is_dmc(controller))
    ans = *controller->csrs->sel2 & DMC_SEL2_M_IEO;
else
    ans = *controller->csrs->sel0 & DMP_SEL0_M_IEO;
return ans;
}

int dmc_is_in_io(CTLR *controller)
{
int ans = 0;

if (dmc_is_dmc(controller))
    ans = ((*controller->csrs->sel0 & DMC_SEL0_M_ITYPE) == DMC_C_TYPE_RBACC);
else
    ans = ((*controller->csrs->sel2 & DMP_SEL2_M_CODE) == DMP_C_TYPE_RBACC);
return ans;
}

int dmc_is_rdyo_set(CTLR *controller)
{
return *controller->csrs->sel2 & DMC_SEL2_M_RDO;
}

void dmc_set_rdyi(CTLR *controller)
{
if (dmc_is_dmc(controller)) {
    dmc_setreg(controller, 0, *controller->csrs->sel0 | DMC_SEL0_M_RDI, DBG_RGC);
    if (controller->dev_type == DMR) {
        dmc_setreg(controller, 4, *controller->modem | 0x0800, DBG_RGC);
        dmc_setreg(controller, 6,
                   (*controller->modem & DMC_SEL4_M_DTR) | DMC_SEL6_M_LMODEM,
                   DBG_RGC);
        }
    }
else
    dmc_setreg(controller, 2, *controller->csrs->sel2 | DMP_SEL2_M_RDI, DBG_RGC);
dmc_setinint(controller);
}

void dmc_clear_rdyi(CTLR *controller)
{
if (dmc_is_dmc(controller))
    dmc_setreg(controller, 0, *controller->csrs->sel0 & ~(DMC_SEL0_M_RDI|DMC_SEL0_M_ITYPE), DBG_RGC);
else
    dmc_setreg(controller, 2, *controller->csrs->sel2 & ~DMP_SEL2_M_RDI, DBG_RGC);
}

void dmc_set_rdyo(CTLR *controller)
{
dmc_setreg(controller, 2, *controller->csrs->sel2 | DMC_SEL2_M_RDO, DBG_RGC);

dmc_setoutint(controller);
}

uint32 dmc_get_addr(CTLR *controller)
{
if (dmc_is_dmc(controller) || (!(*controller->csrs->sel2 & DMP_SEL2_M_22BIT)))
    return ((uint32)dmc_getreg(controller, 4, DBG_RGC))  | ((((uint32)dmc_getreg(controller, 6, DBG_RGC) & 0xc000)) << 2);
else
    return ((uint32)dmc_getreg(controller, 4, DBG_RGC))  | (((uint32)dmc_getreg(controller, 6, DBG_RGC) & 0x3FF) << 16);
}

void dmc_set_addr(CTLR *controller, uint32 addr)
{
if (dmc_is_dmc(controller) || (!(*controller->csrs->sel2 & DMP_SEL2_M_22BIT))) {
    dmc_setreg(controller, 4, addr & 0xFFFF, DBG_RGC);
    dmc_setreg(controller, 6, (uint16)(((addr >> 16) << 14) | (*controller->csrs->sel6 & 0x3FFF)) , DBG_RGC);
    }
else {
    dmc_setreg(controller, 4, addr & 0xFFFF, DBG_RGC);
    dmc_setreg(controller, 6, ((addr >> 16) & 0x3F), DBG_RGC);
    }
}

uint16 dmc_get_count(CTLR *controller)
{
if (dmc_is_dmc(controller) || (!(*controller->csrs->sel2 & DMP_SEL2_M_22BIT)))
    return dmc_getreg(controller, 6, DBG_RGC) & 0x3FFF;
else
    return dmc_getreg(controller, 010, DBG_RGC) & 0x3FFF;
}

void dmc_set_count(CTLR *controller, uint16 count)
{
if (dmc_is_dmc(controller) || (!(*controller->csrs->sel2 & DMP_SEL2_M_22BIT)))
    dmc_setreg(controller, 6, (*controller->csrs->sel6 & 0xc000) | (0x3FFF & count), DBG_RGC);
else
    dmc_setreg(controller, 010, (*controller->csrs->sel10 & 0xc000) | (0x3FFF & count), DBG_RGC);
}

uint8 dmc_get_modem(CTLR *controller)
{
int32 modem_bits;

tmxr_set_get_modem_bits (controller->line, 0, 0, &modem_bits);
*controller->modem &= ~(DMC_SEL4_M_CTS|DMC_SEL4_M_CAR|DMC_SEL4_M_RI|DMC_SEL4_M_DSR|DMC_SEL4_M_DTR|DMC_SEL4_M_RTS);
*controller->modem |= (modem_bits&TMXR_MDM_DCD) ? DMC_SEL4_M_CAR : 0;
*controller->modem |= (modem_bits&TMXR_MDM_CTS) ? DMC_SEL4_M_CTS : 0;
*controller->modem |= (modem_bits&TMXR_MDM_DSR) ? DMC_SEL4_M_DSR : 0;
*controller->modem |= (modem_bits&TMXR_MDM_RNG) ? DMC_SEL4_M_RI : 0;
*controller->modem |= (modem_bits&TMXR_MDM_DTR) ? DMC_SEL4_M_DTR : 0;
*controller->modem |= (modem_bits&TMXR_MDM_RTS) ? DMC_SEL4_M_RTS : 0;
return (*controller->modem);
}

void dmc_set_modem_dtr(CTLR *controller)
{
if (dmc_is_attached(controller->unit) && (!(DMC_SEL4_M_DTR & *controller->modem))) {
    sim_debug(DBG_MDM, controller->device, "%s%d: DTR State Change to UP(ON)\n", controller->device->name, controller->index);
    tmxr_set_get_modem_bits (controller->line, TMXR_MDM_DTR|TMXR_MDM_RTS, 0, NULL);
    dmc_get_modem(controller);
    controller->line->rcve = 1;
    }
}

void dmc_clr_modem_dtr(CTLR *controller)
{
if (*controller->modem & DMC_SEL4_M_DTR) {
    sim_debug(DBG_MDM, controller->device, "%s%d: DTR State Change to DOWN(OFF)\n", controller->device->name, controller->index);
    }
tmxr_set_get_modem_bits (controller->line, 0, TMXR_MDM_DTR|TMXR_MDM_RTS, NULL);
dmc_get_modem(controller);
}

void dmc_set_lost_data(CTLR *controller)
{
dmc_setreg(controller, 6, *controller->csrs->sel6 | DMC_SEL6_M_LOSTDATA, DBG_RGC);
}

void dmc_clear_master_clear(CTLR *controller)
{
dmc_setreg(controller, 0,
           *controller->csrs->sel0 & ~(DMC_SEL0_M_MCLEAR|DMC_SEL0_M_STEPUP|DMC_SEL0_M_ROMI|DMC_SEL0_M_ROMO|DMC_SEL0_M_LU_LOOP|DMC_SEL0_M_STEPLU), DBG_RGC);
}

void dmc_set_run(CTLR *controller)
{
dmc_setreg(controller, 0, *controller->csrs->sel0 | DMC_SEL0_M_RUN, DBG_RGC);
}

int dmc_get_input_transfer_type(CTLR *controller)
{
int ans = 0;

if (dmc_is_dmc(controller))
    ans = *controller->csrs->sel0 & DMC_SEL0_M_ITYPE;
else
    ans = *controller->csrs->sel2 & DMP_SEL2_M_CODE;
return ans;
}

void dmc_set_type_output(CTLR *controller, int type)
{
dmc_setreg(controller, 2, (*controller->csrs->sel2 & ~DMC_SEL2_M_CODE) | (type & DMC_SEL2_M_CODE), DBG_RGC);
}

void dmc_process_master_clear(CTLR *controller)
{
CONTROL_OUT *control;

sim_debug(DBG_INF, controller->device, "%s%d: Master clear\n", controller->device->name, controller->index);
dmc_clear_master_clear(controller);
dmc_clr_modem_dtr(controller);
controller->link.state = Halt;
controller->state = Initialised;
while ((control = controller->control_out)) {
    controller->control_out = control->next;
    free (control);
    }
controller->control_out = NULL;
if (controller->dev_type == DMR) {
    if (dmc_is_attached(controller->unit)) {
        /* Indicates microdiagnostics complete */
        if (((*controller->csrs->sel0 & DMC_SEL0_M_UDIAG) != 0) ^
            (dmc_microdiag[controller->index]))
            dmc_setreg(controller, 2, 0x8000, DBG_RGC);/* Microdiagnostics Complete */
        else
            dmc_setreg(controller, 2, 0x4000, DBG_RGC); /* Microdiagnostics Inhibited */
        }
    else {
        /* Indicate M8203 (Line Unit) test failed */
        dmc_setreg(controller, 2, 0x0200, DBG_RGC);
        }
    }
if (controller->dev_type == DMP) {
    dmc_setreg(controller, 4, 077, DBG_RGC);
    dmc_setreg(controller, 6, 0305, DBG_RGC);
    }
dmc_buffer_queue_init_all(controller);

controller->transfer_state = Idle;
if (dmc_is_attached(controller->unit))
    dmc_set_run(controller);
}

void dmc_start_input_transfer(CTLR *controller)
{
sim_debug(DBG_INF, controller->device, "%s%d: Starting input transfer\n", controller->device->name, controller->index);
controller->transfer_state = InputTransfer;
dmc_set_rdyi(controller);
}

void dmc_start_control_output_transfer(CTLR *controller)
{
if ((!controller->control_out) || 
    (controller->transfer_state != Idle) || 
    (dmc_is_rdyo_set(controller)))
    return;
sim_debug(DBG_INF, controller->device, "%s%d: Starting control output transfer: SEL6 = 0x%04X\n", controller->device->name, controller->index, controller->control_out->sel6);
controller->transfer_state = OutputControl;
dmc_setreg (controller, 6, controller->control_out->sel6, DBG_RGC);
dmc_set_type_output(controller, DMC_C_TYPE_CNTL);
dmc_set_rdyo(controller);
}

void dmc_complete_transmit(CTLR *controller)
{
BUFFER *buffer = controller->link.xmt_buffer;
if (!buffer)
    return;
controller->link.xmt_done_buffer = buffer;
ddcmp_dispatch (controller, DDCMP_EVENT_XMIT_DONE);
controller->link.xmt_done_buffer = NULL;
buffer = (BUFFER *)remqueue (&buffer->hdr);
buffer->actual_bytes_transferred = buffer->count;
controller->buffers_transmitted_to_net++;
if (buffer->type == TransmitData) {
    buffer->actual_bytes_transferred = buffer->count - (DDCMP_HEADER_SIZE + DDCMP_CRC_SIZE);
    ASSURE (insqueue (&buffer->hdr, controller->ack_wait_queue->hdr.prev));
    }
else {
    ASSURE (insqueue (&buffer->hdr, &controller->free_queue->hdr));
    }
controller->link.xmt_buffer = NULL;
}

t_stat dmc_svc(UNIT* uptr)
{
CTLR *controller = dmc_get_controller_from_unit(uptr);
DEVICE *dptr = controller->device;

sim_debug(DBG_TRC, dptr, "dmc_svc(%s%d)\n", controller->device->name, controller->index);

/* Perform delayed register actions */
if (controller->dmc_wr_delay) {
    controller->dmc_wr_delay = 0;
    dmc_process_command(controller);
    if (controller->link.xmt_buffer) {
        sim_activate_notbefore (uptr, controller->link.xmt_buffer->buffer_return_time);
        return SCPE_OK;
        }
    }
if (dmc_is_attached(controller->unit)) {
    /* Speed limited transmissions are completed here after the appropriate service delay */
    if (controller->link.xmt_buffer) {
        dmc_complete_transmit(controller);
        dmc_ddcmp_start_transmitter (controller);
        }

    dmc_start_control_output_transfer(controller);

    if (controller->xmt_queue->count)
        ddcmp_dispatch (controller, 0);
    if (controller->transfer_state == Idle)
        dmc_start_transfer_buffer(controller);

    dmc_buffer_fill_receive_buffers(controller);
    if (controller->transfer_state == Idle)
        dmc_start_transfer_buffer(controller);
    /* Execution of this routine normally is triggered by programatic access by the
       simulated system's device driver to the device registers or the arrival of 
       traffic from the network.  Network traffic is handled by the dmc_poll_svc 
       which may call this routine as needed.  Direct polling here is extra overhead 
       and is only scheduled when needed.
     */
    if ((controller->completion_queue->count) ||    /* if completion queue not empty? */
        (controller->control_out)             ||    /*    or pending control outs? */
        (controller->transfer_state != Idle))       /*    or registers are busy */
        sim_clock_coschedule (uptr, tmxr_poll);     /* wake up periodically until these don't exist */
    }

return SCPE_OK;
}

t_stat dmc_poll_svc (UNIT *uptr)
{
DEVICE *dptr = ((CTLR *)uptr->ctlr)->device;
CTLR *controller;
TMXR *mp = (dptr == &dmc_dev) ? &dmc_desc : &dmp_desc;
int32 dmc, active, attached;

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

dmc = tmxr_poll_conn(mp);
if (dmc >= 0) {                                 /* new connection? */
    controller = (CTLR *)dptr->units[dmc].ctlr;
    dmc_get_modem (controller);
    sim_debug(DBG_MDM, dptr, "dmc_poll_svc(dmc=%d) - Connection State Change to UP(ON)\n", dmc);
    ddcmp_dispatch (controller, 0);
    sim_clock_coschedule (controller->unit, tmxr_poll); /* be sure to wake up soon to continue processing */
    }
tmxr_poll_rx (mp);
tmxr_poll_tx (mp);
for (dmc=active=attached=0; dmc < mp->lines; dmc++) {
    CTLR *controller = (CTLR *)dptr->units[dmc].ctlr;
    TMLN *lp = controller->line;
    uint8 old_modem, new_modem;

    controller = (CTLR *)dptr->units[dmc].ctlr;
    if (controller->unit->flags & UNIT_ATT)
        ++attached;
    if (lp->conn)
        ++active;
    old_modem = *controller->modem;
    new_modem = dmc_get_modem (controller);
    if ((old_modem & DMC_SEL4_M_CAR) && 
        (!(new_modem & DMC_SEL4_M_CAR))) {
        sim_debug(DBG_MDM, controller->device, "dmc_poll_svc(dmc=%d) - Connection State Change to %s\n", dmc, (new_modem & DMC_SEL4_M_CAR) ? "UP(ON)" : "DOWN(OFF)");
        ddcmp_dispatch (controller, 0);
        sim_clock_coschedule (controller->unit, tmxr_poll); /* wake up soon to finish processing */
        }
    if ((lp->xmte && tmxr_tpbusyln(lp)) || 
        (lp->xmte && controller->link.xmt_buffer) ||
        ddcmp_UserSendMessage(controller)) {
        if (tmxr_tpbusyln(lp)) {
            sim_debug(DBG_DAT, controller->device, "dmc_poll_svc(dmc=%d) - Packet Transmission of remaining %d bytes restarting...\n", dmc, tmxr_tpqln (lp));
            }
        dmc_svc (&controller->device->units[dmc]);  /* Flush pending output */
        }
    if (dmc_buffer_fill_receive_buffers(controller))
        dmc_svc (&controller->device->units[dmc]);  /* return ACKed transmit packets */
    }
if (active)
    sim_clock_coschedule (uptr, tmxr_poll);         /* reactivate */
else {
    for (dmc=0; dmc < mp->lines; dmc++) {
        uint32 *speeds = (dptr == &dmc_dev) ? dmc_speed : dmp_speed;
        CTLR *controller = (CTLR *)dptr->units[dmc].ctlr;

        if (speeds[dmc]/8)
            controller->byte_wait = (tmr_poll*clk_tps)/(speeds[dmc]/8);
        else
            controller->byte_wait = 0;
        }
    if (attached)
        sim_activate_after (uptr, DMC_CONNECT_POLL*1000000);/* periodic check for connections */
    }
return SCPE_OK;
}

t_stat dmc_timer_svc (UNIT *uptr)
{
int32 dmc, active;
DEVICE *dptr = (UNIBUS) ? ((&dmc_dev == find_dev_from_unit(uptr)) ? &dmc_dev : &dmp_dev) : &dmv_dev;
TMXR *mp = (dptr == &dmc_dev) ? &dmc_desc : &dmp_desc;
CTLR *controller;

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

for (dmc=active=0; dmc < mp->lines; dmc++) {
    controller = (CTLR *)dptr->units[dmc].ctlr;
    if (controller->link.TimerRunning) {
        if (controller->link.TimeRemaining > 0) {
            --controller->link.TimeRemaining;
            if (controller->link.TimeRemaining > 0)
                ++active;
            else
                ddcmp_dispatch (controller, DDCMP_EVENT_TIMER);  /* Timer Expiring Event */
            }
        }
    }
if (active)
    sim_activate_after (uptr, 1000000);         /* Per Second Event trigger */
return SCPE_OK;
}

void dmc_buffer_queue_init(CTLR *controller, BUFFER_QUEUE *q, const char *name, size_t size, BUFFER *buffers)
{
q->name = name;
initqueue (&q->hdr, q, size, buffers, sizeof(*buffers));
q->controller = controller;
}

t_bool dmc_transmit_queue_empty(CTLR *controller)
{
return (controller->xmt_queue->count == 0);
}

void dmc_buffer_queue_init_all(CTLR *controller)
{
size_t queue_size = (controller->dev_type == DMC) ? DMC_QUEUE_SIZE : 
                                                    ((controller->dev_type == DMR) ? DMR_QUEUE_SIZE : 
                                                                                        DMP_QUEUE_SIZE);
queue_size *= 2;
*controller->buffers = (BUFFER *)realloc(*controller->buffers, queue_size*sizeof(**controller->buffers));
memset (*controller->buffers, 0, queue_size*sizeof(**controller->buffers));
dmc_buffer_queue_init(controller, controller->rcv_queue,        "receive",    0, NULL);
dmc_buffer_queue_init(controller, controller->completion_queue, "completion", 0, NULL);
dmc_buffer_queue_init(controller, controller->xmt_queue,        "transmit",   0, NULL);
dmc_buffer_queue_init(controller, controller->ack_wait_queue,   "ack wait",   0, NULL);
dmc_buffer_queue_init(controller, controller->free_queue,       "free",       queue_size, *controller->buffers);
}

BUFFER *dmc_buffer_allocate(CTLR *controller)
{
BUFFER *buffer = (BUFFER *)remqueue (controller->free_queue->hdr.next);

if (!buffer) {
    sim_printf ("DDCMP Buffer allocation failure.\n");
    sim_printf ("This is a fatal error which should never happen.\n");
    sim_printf ("Please submit this output when you report this bug.\n");
    dmc_showqueues (stdout, controller->unit, 0, NULL);
    dmc_showstats (stdout, controller->unit, 0, NULL);
    dmc_showddcmp (stdout, controller->unit, 0, NULL);
    if (sim_log) {
        dmc_showqueues (sim_log, controller->unit, 0, NULL);
        dmc_showstats (sim_log, controller->unit, 0, NULL);
        dmc_showddcmp (sim_log, controller->unit, 0, NULL);
        fflush (sim_log);
        }
    if (sim_deb) {
        dmc_showqueues (sim_deb, controller->unit, 0, NULL);
        dmc_showstats (sim_deb, controller->unit, 0, NULL);
        dmc_showddcmp (sim_deb, controller->unit, 0, NULL);
        fflush (sim_deb);
        }
    return buffer;
    }
buffer->address = 0;
buffer->count = 0;
free (buffer->transfer_buffer);
buffer->transfer_buffer = NULL;
buffer->actual_bytes_transferred = 0;
buffer->type = TransmitControl;         /* Default */
buffer->buffer_return_time = 0;
return buffer;
}

BUFFER *dmc_buffer_queue_add(BUFFER_QUEUE *q, uint32 address, uint16 count, BufferType type)
{
BUFFER *buffer = dmc_buffer_allocate(q->controller);

if (buffer) {
    buffer->type = type;
    buffer->address = address;
    buffer->count = count;
    ASSURE (insqueue (&buffer->hdr, q->hdr.prev)); /* Insert at tail */
    sim_debug(DBG_INF, q->controller->device, "%s%d: Queued %s buffer address=0x%08x count=%d %s\n", q->controller->device->name, q->controller->index, q->name, address, count, controller_queue_state(q->controller));
    }
else {
    sim_debug(DBG_WRN, q->controller->device, "%s%d: Failed to queue %s buffer address=0x%08x, queue full %s\n", q->controller->device->name, q->controller->index, q->name, address, controller_queue_state(q->controller));
    // TODO: Report error here.
    }
return buffer;
}


BUFFER *dmc_buffer_queue_head(BUFFER_QUEUE *q)
{
return ((q->hdr.next == &q->hdr) ? NULL : (BUFFER *)q->hdr.next);
}

void dmc_queue_control_out(CTLR *controller, uint16 sel6)
{
CONTROL_OUT *control = (CONTROL_OUT *)calloc(1, sizeof(*control));
CONTROL_OUT *last = NULL;

control->sel6 = sel6;
if (controller->control_out) {
    last = controller->control_out;
    while (last->next)
        last = last->next;
    last->next = control;
    }
else
    controller->control_out = control;
}

/* returns true if some data was received */
t_bool dmc_buffer_fill_receive_buffers(CTLR *controller)
{
int ans = FALSE;

if (controller->state == Running) {
    BUFFER *buffer = dmc_buffer_queue_head(controller->rcv_queue);

    while (buffer) {
        ddcmp_tmxr_get_packet_ln (controller->line, (const uint8 **)&controller->link.rcv_pkt, &controller->link.rcv_pkt_size, *controller->corruption_factor);
        if (!controller->link.rcv_pkt)
            break;
        ans = TRUE;
        controller->buffers_received_from_net++;
        controller->ddcmp_control_packets_received += (controller->link.rcv_pkt[0] == DDCMP_ENQ) ? 1 : 0;
        ddcmp_dispatch(controller, DDCMP_EVENT_PKT_RCVD);
        buffer = dmc_buffer_queue_head(controller->rcv_queue);
        }
    }
return ans;
}

void dmc_start_transfer_buffer(CTLR *controller)
{
BUFFER *head = dmc_buffer_queue_head(controller->completion_queue);
uint16 count;

if ((!head) ||
    (controller->transfer_state != Idle) ||
    (dmc_is_rdyo_set(controller)))
    return;
count = (uint16)head->actual_bytes_transferred;
switch (head->type) {
    case Receive:
        sim_debug(DBG_INF, controller->device, "%s%d: Starting data output transfer for receive, address=0x%08x, count=%d %s\n", controller->device->name, controller->index, head->address, count, controller_queue_state(controller));
        dmc_set_type_output(controller, DMC_C_TYPE_RBACC);
        break;
    case TransmitData:
        sim_debug(DBG_INF, controller->device, "%s%d: Starting data output transfer for transmit, address=0x%08x, count=%d %s\n", controller->device->name, controller->index, head->address, count, controller_queue_state(controller));
        dmc_set_type_output(controller, DMC_C_TYPE_XBACC);
        break;
    default:
        break;
    }
dmc_set_addr(controller, head->address);
dmc_set_count(controller, count);
controller->transfer_state = OutputTransfer;
dmc_set_rdyo(controller);
}

void dmc_check_for_output_transfer_completion(CTLR *controller)
{
BUFFER *buffer;

if ((dmc_is_rdyo_set(controller)) ||
    (controller->transfer_state != OutputTransfer))
    return;
sim_debug(DBG_INF, controller->device, "%s%d: Output transfer completed %s\n", controller->device->name, controller->index, controller_queue_state(controller));
buffer = (BUFFER *)remqueue (controller->completion_queue->hdr.next);
ASSURE (insqueue (&buffer->hdr, controller->free_queue->hdr.prev));
controller->transmit_buffer_output_transfers_completed++;
controller->transfer_state = Idle;
dmc_process_command(controller); // check for any other transfers
}

void dmc_check_for_output_control_completion(CTLR *controller)
{
CONTROL_OUT *control = controller->control_out;

if ((dmc_is_rdyo_set(controller)) ||
    (controller->transfer_state != OutputControl))
    return;

dmc_dumpregsel6(controller, DBG_INF, "Output command completed:", control->sel6);

controller->transfer_state = Idle;
controller->control_out = control->next;
free(control);
controller->control_out_operations_completed++;
dmc_process_command(controller); // check for any other transfers
}

void dmc_process_input_transfer_completion(CTLR *controller)
{
if (dmc_is_dmc(controller)) {
    if (dmc_is_run_set(controller) && 
        (!dmc_is_rqi_set(controller))) {
        /* Time to decode input command arguments */
        uint16 sel6 = *controller->csrs->sel6;
        uint32 addr = dmc_get_addr(controller);
        uint16 count = dmc_get_count(controller);

        controller->transfer_type = dmc_get_input_transfer_type(controller);
        dmc_clear_rdyi(controller);
        switch (controller->transfer_type) {
            case DMC_C_TYPE_BASEIN:
                *controller->baseaddr = dmc_get_addr(controller);
                *controller->basesize = dmc_get_count(controller);
                sim_debug(DBG_INF, controller->device, "%s%d: Completing Base In input transfer, base address=0x%08x count=%d\n", controller->device->name, controller->index, *controller->baseaddr, *controller->basesize);
                break;
            case DMC_C_TYPE_XBACC:
                if (1) {
                    BUFFER *buffer = dmc_buffer_queue_add(controller->xmt_queue, addr, count, TransmitData);
                    int n;

                    /* construct buffer - leaving room for DDCMP header and CRCs */
                    buffer->transfer_buffer = (uint8 *)realloc (buffer->transfer_buffer, buffer->count + DDCMP_HEADER_SIZE + DDCMP_CRC_SIZE);
                    memset (buffer->transfer_buffer, 0, DDCMP_HEADER_SIZE);
                    n = Map_ReadB (buffer->address, buffer->count, buffer->transfer_buffer + DDCMP_HEADER_SIZE);
                    if (n > 0) {
                        sim_debug(DBG_WRN, controller->device, "%s%d: DMA error\n", controller->device->name, controller->index);
                        }
                    buffer->count += DDCMP_HEADER_SIZE + DDCMP_CRC_SIZE;
                    controller->transmit_buffer_input_transfers_completed++;
                    }
                if (controller->state != Running) {
                    controller->state = Running;
                    dmc_set_modem_dtr (controller);
                    }
                ddcmp_dispatch (controller, 0);
                break;
            case DMC_C_TYPE_RBACC:
                dmc_buffer_queue_add(controller->rcv_queue, addr, count, Receive);
                dmc_buffer_fill_receive_buffers(controller);
                controller->receive_buffer_input_transfers_completed++;
                ddcmp_dispatch (controller, 0);
                break;
            case DMC_C_TYPE_CNTL:
                sim_debug(DBG_INF, controller->device, "%s%d: Control In Command Received(%s MODE%s%s)\n", 
                                                       controller->device->name, controller->index,
                                                       (sel6&DMC_SEL6_M_MAINT) ? "MAINTENANCE" : "DDCMP", 
                                                       (sel6&DMC_SEL6_M_HDX) ? ", Half Duples" : "", 
                                                       (sel6&DMC_SEL6_M_LONGSTRT) ? ", Long Start Timer" : "");
                dmc_set_modem_dtr (controller);
                controller->transfer_state = Idle;
                ddcmp_dispatch (controller, (sel6&DMC_SEL6_M_MAINT) ? DDCMP_EVENT_MAINTMODE : 0);
                return;
            case DMC_C_TYPE_HALT:
                sim_debug(DBG_INF, controller->device, "%s%d: Halt Command Received\n", controller->device->name, controller->index);
                controller->state = Halted;
                ddcmp_dispatch(controller, 0);
                dmc_clr_modem_dtr(controller);
                dmc_queue_control_out(controller, DMC_SEL6_M_HALTCOMP);
                return;
            }
        controller->transfer_state = Idle;
        dmc_process_command (controller);
        }
    }
else {  /* DMP */
    if (!dmc_is_rdyi_set(controller)) {
        uint16 sel6 = *controller->csrs->sel6;

        if (controller->transfer_type == DMP_C_TYPE_MODE) {
            uint16 mode = sel6 & DMP_TYPE_INPUT_MASK;
            const char * duplex = (mode & 1) ? "Full-Duplex" : "Half-Duplex";
            const char * config;

            if (mode & 4)
                config = "Point-to-point";
            else
                config = (mode & 2) ? "Tributary station" : "Control Station";

            sim_debug(DBG_INF, controller->device, "%s%d: Completing Mode input transfer, %s %s\n", controller->device->name, controller->index, duplex, config);
            }
        else 
            if (controller->transfer_type == DMP_C_TYPE_CNTL) {
                sim_debug(DBG_WRN, controller->device, "%s%d: Control command (not processed yet)\n", controller->device->name, controller->index);
                }
            else 
                if (controller->transfer_type == DMP_C_TYPE_RBACC) {
                    sim_debug(DBG_WRN, controller->device, "%s%d: Receive Buffer command (not processed yet)\n", controller->device->name, controller->index);
                    }
                else 
                    if (controller->transfer_type == DMP_C_TYPE_XBACC) {
                        sim_debug(DBG_WRN, controller->device, "%s%d: Transmit Buffer command (not processed yet)\n", controller->device->name, controller->index);
                        }
                    else {
                        sim_debug(DBG_WRN, controller->device, "%s%d: Unrecognised command code 0x%X\n", controller->device->name, controller->index, controller->transfer_type);
                        }

        controller->transfer_state = Idle;
        }
    }
}

typedef enum {
    EQ,
    NE,
    LT,
    LE,
    GT,
    GE
} CompareOP;

/* DDCMP Modulo 256 compare - from Rob Jarratt */
static int Mod256Cmp(uint8 a, uint8 b, size_t queue_size)
{
int ans;
int abdiff = (int)b - (int)a;
int badiff = (int)a - (int)b;
 
if (abdiff == 0)
    ans = 0;
else 
    if (abdiff < 0)
        ans = -1;
    else
        ans = 1;
 
if (abs(badiff) <= (int)queue_size)
    ans = -1 * ans;
 
return ans;
}

t_bool ddcmp_compare (uint8 a, CompareOP Op, uint8 b, CTLR *controller)
{
int cmp = Mod256Cmp(a & 0xFF, b & 0xFF, controller->free_queue->size);

switch (Op) {
    case EQ:
        return (cmp == 0);
    case NE:
        return (cmp != 0);
    case LE:
        if (cmp == 0)
            return TRUE;
    case LT:
        return (cmp < 0);
    case GE:
        if (cmp == 0)
            return TRUE;
    case GT:
        return (cmp > 0);
    default:    /* Never happens */
        return FALSE;
    }
}

void ddcmp_StartTimer             (CTLR *controller)
{
controller->link.TimerRunning = TRUE;
controller->link.TimeRemaining = DDCMP_PACKET_TIMEOUT;
sim_activate_after (controller->device->units+(controller->device->numunits-1), controller->link.TimeRemaining*1000000);
}
void ddcmp_StopTimer              (CTLR *controller)
{
controller->link.TimerRunning = FALSE;
}
void ddcmp_ResetVariables         (CTLR *controller)
{
size_t data_packets = 0;
BUFFER *buffer;

controller->link.R = 0;
controller->link.N = 0;
controller->link.A = 0;
controller->link.T = 1;
controller->link.X = 0;
controller->link.SACK = FALSE;
controller->link.SNAK = FALSE;
controller->link.SREP = FALSE;
controller->link.state = Halt;
controller->link.nak_reason = 0;
/* Move any ack wait packets back to the transmit queue so they get 
   resent when the link is restored */
while (controller->ack_wait_queue->count) {
    buffer = (BUFFER *)remqueue (controller->ack_wait_queue->hdr.prev);
    memset (buffer->transfer_buffer, 0, DDCMP_HEADER_SIZE);
    ASSURE (insqueue (&buffer->hdr, &controller->xmt_queue->hdr));
    }
/* Also make sure that the transmit queue has no control packets in it 
   and that any non transmit buffer(s) have zeroed headers so they will
   be properly constructed when the link comes up */
buffer = (BUFFER *)controller->xmt_queue->hdr.next;
while (controller->xmt_queue->count - data_packets) {
    if (((BUFFER *)controller->xmt_queue->hdr.next)->transfer_buffer[0] == DDCMP_ENQ) {
        BUFFER *buffer_next = (BUFFER *)buffer->hdr.next;

        buffer = (BUFFER *)remqueue (&buffer->hdr);
        ASSURE (insqueue (&buffer->hdr, &controller->free_queue->hdr));
        buffer = buffer_next;
        continue;
        }
    ++data_packets;
    memset (buffer->transfer_buffer, 0, DDCMP_HEADER_SIZE);
    buffer = (BUFFER *)buffer->hdr.next;
    }
}
void ddcmp_SendStrt               (CTLR *controller)
{
BUFFER *buffer = dmc_buffer_allocate(controller);

buffer->transfer_buffer = (uint8 *)malloc (DDCMP_HEADER_SIZE);
buffer->count = DDCMP_HEADER_SIZE;
ddcmp_build_start_packet (buffer->transfer_buffer);
ASSURE (insqueue (&buffer->hdr, &controller->xmt_queue->hdr));
dmc_ddcmp_start_transmitter (controller);
}
void ddcmp_SendStack              (CTLR *controller)
{
BUFFER *buffer = dmc_buffer_allocate(controller);

buffer->transfer_buffer = (uint8 *)malloc (DDCMP_HEADER_SIZE);
buffer->count = DDCMP_HEADER_SIZE;
ddcmp_build_start_ack_packet (buffer->transfer_buffer);
ASSURE (insqueue (&buffer->hdr, &controller->xmt_queue->hdr));
dmc_ddcmp_start_transmitter (controller);
}
void ddcmp_SendAck                (CTLR *controller)
{
BUFFER *buffer = dmc_buffer_allocate(controller);

buffer->transfer_buffer = (uint8 *)malloc (DDCMP_HEADER_SIZE);
buffer->count = DDCMP_HEADER_SIZE;
ddcmp_build_ack_packet (buffer->transfer_buffer, controller->link.R, DDCMP_FLAG_SELECT);
ASSURE (insqueue (&buffer->hdr, &controller->xmt_queue->hdr));
dmc_ddcmp_start_transmitter (controller);
}
void ddcmp_SendNak                (CTLR *controller)
{
BUFFER *buffer = dmc_buffer_allocate(controller);

buffer->transfer_buffer = (uint8 *)malloc (DDCMP_HEADER_SIZE);
buffer->count = DDCMP_HEADER_SIZE;
ddcmp_build_nak_packet (buffer->transfer_buffer, controller->link.nak_reason, controller->link.R, DDCMP_FLAG_SELECT);
ASSURE (insqueue (&buffer->hdr, &controller->xmt_queue->hdr));
dmc_ddcmp_start_transmitter (controller);
}
void ddcmp_SendRep                (CTLR *controller)
{
BUFFER *buffer = dmc_buffer_allocate(controller);

buffer->transfer_buffer = (uint8 *)malloc (DDCMP_HEADER_SIZE);
buffer->count = DDCMP_HEADER_SIZE;
ddcmp_build_rep_packet (buffer->transfer_buffer, controller->link.N, DDCMP_FLAG_SELECT);
ASSURE (insqueue (&buffer->hdr, &controller->xmt_queue->hdr));
dmc_ddcmp_start_transmitter (controller);
}
void ddcmp_SetSACK                (CTLR *controller)
{
controller->link.SACK = TRUE;
}
void ddcmp_ClearSACK              (CTLR *controller)
{
controller->link.SACK = FALSE;
}
void ddcmp_SetSNAK                (CTLR *controller)
{
controller->link.SNAK = TRUE;
}
void ddcmp_ClearSNAK              (CTLR *controller)
{
controller->link.SNAK = FALSE;
}
void ddcmp_SetSREP                (CTLR *controller)
{
controller->link.SREP = TRUE;
}
void ddcmp_ClearSREP              (CTLR *controller)
{
controller->link.SREP = FALSE;
}
void ddcmp_IncrementR             (CTLR *controller)
{
controller->link.R++;
}
void ddcmp_SetAeqRESP             (CTLR *controller)
{
controller->link.A = controller->link.rcv_pkt[DDCMP_RESP_OFFSET];
}
void ddcmp_SetTequalAplus1        (CTLR *controller)
{
controller->link.T = controller->link.A + 1;
}
void ddcmp_IncrementT             (CTLR *controller)
{
controller->link.T++;
}
void ddcmp_SetNAKreason3          (CTLR *controller)
{
controller->link.nak_reason = 3;
}
void ddcmp_SetNAKReasonCRCError   (CTLR *controller)
{
controller->link.nak_reason = controller->link.nak_crc_reason;
}
void ddcmp_NAKMissingPacket       (CTLR *controller)
{
uint8 R = controller->link.R;
QH *qh = &controller->xmt_queue->hdr;

while (ddcmp_compare (controller->link.rcv_pkt[DDCMP_NUM_OFFSET], GE, R, controller)) {
    BUFFER *buffer = dmc_buffer_allocate(controller);

    if (NULL == buffer) {
        sim_debug(DBG_INF, controller->device, "%s%d: No Buffers cause NAKMissingPackets to stop %s\n", controller->device->name, controller->index, controller_queue_state(controller));
        break;
        }
    if (ddcmp_compare (controller->link.rcv_pkt[DDCMP_NUM_OFFSET], GE, controller->link.NAKed, controller)) {
        sim_debug(DBG_INF, controller->device, "%s%d: NAK for prior missing packet %d already sent, still waiting %s\n", controller->device->name, controller->index, controller->link.NAKed, controller_queue_state(controller));
        ASSURE (insqueue (&buffer->hdr, &controller->free_queue->hdr)); /* Release buffer */
        break;
        }
    buffer->transfer_buffer = (uint8 *)malloc (DDCMP_HEADER_SIZE);
    buffer->count = DDCMP_HEADER_SIZE;
    ddcmp_build_nak_packet (buffer->transfer_buffer, 2, R, DDCMP_FLAG_SELECT);
    controller->link.NAKed = R;
    R = R + 1;
    ASSURE (insqueue (&buffer->hdr, qh));
    qh = &buffer->hdr;
    break; /* Only generate a single NAK */
    }
dmc_ddcmp_start_transmitter (controller);
}
void ddcmp_IfTleAthenSetTeqAplus1 (CTLR *controller)
{
if (ddcmp_compare (controller->link.T, LE, controller->link.A, controller))
    controller->link.T = controller->link.A + 1;
}
void ddcmp_IfAltXthenStartTimer   (CTLR *controller)
{
if (ddcmp_compare (controller->link.A, LT, controller->link.X, controller))
    ddcmp_StartTimer (controller);
}
void ddcmp_IfAgeXthenStopTimer    (CTLR *controller)
{
if (ddcmp_compare (controller->link.A, GE, controller->link.X, controller))
    ddcmp_StopTimer (controller);
}
void ddcmp_Ignore                 (CTLR *controller)
{
}
void ddcmp_GiveBufferToUser       (CTLR *controller)
{
BUFFER *buffer = (BUFFER *)remqueue (controller->rcv_queue->hdr.next);

if (!buffer) {
    ddcmp_SetSNAK (controller);
    controller->link.nak_reason = 8;    /* buffer temporarily unavailable */
    return;
    }

buffer->actual_bytes_transferred = controller->link.rcv_pkt_size - (DDCMP_HEADER_SIZE + DDCMP_CRC_SIZE);
Map_WriteB(buffer->address, buffer->actual_bytes_transferred, controller->link.rcv_pkt + DDCMP_HEADER_SIZE);
ASSURE (insqueue (&buffer->hdr, controller->completion_queue->hdr.prev));     /* Insert at tail */
ddcmp_IncrementR (controller);
ddcmp_SetSACK (controller);
}
void ddcmp_CompleteAckedTransmits (CTLR *controller)
{
BUFFER *buffer = dmc_buffer_queue_head(controller->ack_wait_queue);

while (buffer != NULL) {
    if ((!buffer->transfer_buffer) || 
        (!controller->link.rcv_pkt) ||
        ddcmp_compare (buffer->transfer_buffer[DDCMP_NUM_OFFSET], GT, controller->link.rcv_pkt[DDCMP_RESP_OFFSET], controller))
        break;
    buffer = (BUFFER *)remqueue (&buffer->hdr);
    ASSURE (insqueue (&buffer->hdr, controller->completion_queue->hdr.prev)); /* Insert at tail */
    buffer = dmc_buffer_queue_head(controller->ack_wait_queue);
    }
}
void ddcmp_ReTransmitMessageT (CTLR *controller)
{
BUFFER *buffer = dmc_buffer_queue_head(controller->ack_wait_queue);
size_t i;
uint8 T = controller->link.T;

for (i=0; i < controller->ack_wait_queue->count; ++i) {
    if ((!buffer->transfer_buffer) || 
        ddcmp_compare (buffer->transfer_buffer[DDCMP_NUM_OFFSET], NE, T, controller)) {
        buffer = (BUFFER *)buffer->hdr.next;
        continue;
        }
    ddcmp_build_data_packet (buffer->transfer_buffer, buffer->count - (DDCMP_HEADER_SIZE + DDCMP_CRC_SIZE), DDCMP_FLAG_SELECT|DDCMP_FLAG_QSYNC, T, controller->link.R);
    buffer = (BUFFER *)remqueue (&buffer->hdr);
    ASSURE (insqueue (&buffer->hdr, controller->xmt_queue->hdr.prev)); /* Insert at tail */
    break;
    }
}
void ddcmp_NotifyDisconnect       (CTLR *controller)
{
dmc_queue_control_out(controller, DMC_SEL6_M_DISCONN);
}
void ddcmp_NotifyStartRcvd        (CTLR *controller)
{
dmc_queue_control_out(controller, DMC_SEL6_M_STRTRCVD);
}
void ddcmp_NotifyMaintRcvd        (CTLR *controller)
{
dmc_queue_control_out(controller, DMC_SEL6_M_MAINTRCV);
}
void ddcmp_SendDataMessage        (CTLR *controller)
{
BUFFER *buffer = dmc_buffer_queue_head(controller->xmt_queue);

ddcmp_build_data_packet (buffer->transfer_buffer, buffer->count - (DDCMP_HEADER_SIZE + DDCMP_CRC_SIZE), DDCMP_FLAG_SELECT|DDCMP_FLAG_QSYNC, controller->link.N + 1, controller->link.R);
controller->link.N += 1;
controller->link.T = controller->link.N + 1;
controller->link.SACK = 0;
dmc_ddcmp_start_transmitter (controller);
}
void ddcmp_SendMaintMessage       (CTLR *controller)
{
BUFFER *buffer = dmc_buffer_queue_head(controller->xmt_queue);

ddcmp_build_maintenance_packet (buffer->transfer_buffer, buffer->count - (DDCMP_HEADER_SIZE + DDCMP_CRC_SIZE));
dmc_ddcmp_start_transmitter (controller);
}
void ddcmp_SetXSetNUM             (CTLR *controller)
{
controller->link.X = controller->link.xmt_done_buffer->transfer_buffer[DDCMP_NUM_OFFSET];
}

/* Conditions/Events */

t_bool ddcmp_UserHalt             (CTLR *controller)
{
return (controller->state == Halted);
}
t_bool ddcmp_UserStartup          (CTLR *controller)
{
return (*controller->modem & DMC_SEL4_M_DTR);
}
t_bool ddcmp_UserMaintenanceMode  (CTLR *controller)
{
return (controller->link.ScanningEvents & DDCMP_EVENT_MAINTMODE);
}
t_bool ddcmp_ReceiveStack         (CTLR *controller)
{
return ((controller->link.ScanningEvents & DDCMP_EVENT_PKT_RCVD) &&
        controller->link.rcv_pkt &&
        (controller->link.rcv_pkt[0] == DDCMP_ENQ) &&
        (controller->link.rcv_pkt[1] == DDCMP_CTL_STACK) &&
        (!ddcmp_ReceiveMessageError(controller)));
}
t_bool ddcmp_ReceiveStrt          (CTLR *controller)
{
return ((controller->link.ScanningEvents & DDCMP_EVENT_PKT_RCVD) &&
        controller->link.rcv_pkt &&
        (controller->link.rcv_pkt[0] == DDCMP_ENQ) &&
        (controller->link.rcv_pkt[1] == DDCMP_CTL_STRT) &&
        (!ddcmp_ReceiveMessageError(controller)));
}
t_bool ddcmp_TimerRunning         (CTLR *controller)
{
return (controller->link.TimerRunning);
}
t_bool ddcmp_TimerNotRunning      (CTLR *controller)
{
return (!controller->link.TimerRunning);
}
t_bool ddcmp_TimerExpired         (CTLR *controller)
{
return (controller->link.ScanningEvents & DDCMP_EVENT_TIMER);
}
t_bool ddcmp_ReceiveMaintMessage  (CTLR *controller)
{
return ((controller->link.ScanningEvents & DDCMP_EVENT_PKT_RCVD) && 
        controller->link.rcv_pkt &&
        (controller->link.rcv_pkt[0] == DDCMP_DLE) && 
        (!ddcmp_ReceiveMessageError(controller)));
}
t_bool ddcmp_ReceiveAck           (CTLR *controller)
{
return ((controller->link.ScanningEvents & DDCMP_EVENT_PKT_RCVD) &&
        controller->link.rcv_pkt &&
        (controller->link.rcv_pkt[0] == DDCMP_ENQ) &&
        (controller->link.rcv_pkt[1] == DDCMP_CTL_ACK) &&
        (!ddcmp_ReceiveMessageError(controller)));
}
t_bool ddcmp_ReceiveNak           (CTLR *controller)
{
return ((controller->link.ScanningEvents & DDCMP_EVENT_PKT_RCVD) &&
        controller->link.rcv_pkt &&
        (controller->link.rcv_pkt[0] == DDCMP_ENQ) &&
        (controller->link.rcv_pkt[1] == DDCMP_CTL_NAK) &&
        (!ddcmp_ReceiveMessageError(controller)));
}
t_bool ddcmp_ReceiveRep           (CTLR *controller)
{
return ((controller->link.ScanningEvents & DDCMP_EVENT_PKT_RCVD) &&
        controller->link.rcv_pkt &&
        (controller->link.rcv_pkt[0] == DDCMP_ENQ) &&
        (controller->link.rcv_pkt[1] == DDCMP_CTL_REP) &&
        (!ddcmp_ReceiveMessageError(controller)));
}
t_bool ddcmp_NUMEqRplus1          (CTLR *controller)
{
t_bool breturn = (controller->link.rcv_pkt &&
                  ddcmp_compare (controller->link.rcv_pkt[DDCMP_NUM_OFFSET], EQ, controller->link.R + 1, controller));
return breturn;
}
t_bool ddcmp_NUMGtRplus1          (CTLR *controller)
{
t_bool breturn = (controller->link.rcv_pkt &&
                  ddcmp_compare (controller->link.rcv_pkt[DDCMP_NUM_OFFSET], GT, controller->link.R + 1, controller));
return breturn;
}
t_bool ddcmp_ReceiveDataMsg       (CTLR *controller)
{
t_bool breturn = ((controller->link.ScanningEvents & DDCMP_EVENT_PKT_RCVD) &&
                  controller->link.rcv_pkt &&
                  (controller->link.rcv_pkt[0] == DDCMP_SOH) &&
                  (!ddcmp_ReceiveMessageError(controller)));
if (breturn)
    return breturn;
return breturn;
}
t_bool ddcmp_ReceiveMaintMsg      (CTLR *controller)
{
t_bool breturn = ((controller->link.ScanningEvents & DDCMP_EVENT_PKT_RCVD) &&
                  controller->link.rcv_pkt &&
                  (controller->link.rcv_pkt[0] == DDCMP_DLE) &&
                  (!ddcmp_ReceiveMessageError(controller)));
if (breturn)
    return breturn;
return breturn;
}
t_bool ddcmp_ALtRESPleN           (CTLR *controller)
{
return (controller->link.rcv_pkt &&
        ddcmp_compare (controller->link.A, LT, controller->link.rcv_pkt[DDCMP_RESP_OFFSET], controller) &&
        ddcmp_compare (controller->link.rcv_pkt[DDCMP_RESP_OFFSET], LE, controller->link.N, controller));
}
t_bool ddcmp_ALeRESPleN           (CTLR *controller)
{
return (controller->link.rcv_pkt &&
        ddcmp_compare (controller->link.A, LE, controller->link.rcv_pkt[DDCMP_RESP_OFFSET], controller) &&
        ddcmp_compare (controller->link.rcv_pkt[DDCMP_RESP_OFFSET], LE, controller->link.N, controller));
}
t_bool ddcmp_RESPleAOrRESPgtN     (CTLR *controller)
{
return (controller->link.rcv_pkt &&
        (ddcmp_compare (controller->link.rcv_pkt[DDCMP_RESP_OFFSET], LE, controller->link.A, controller) ||
         ddcmp_compare (controller->link.rcv_pkt[DDCMP_RESP_OFFSET], GT, controller->link.N, controller)));
}
t_bool ddcmp_TltNplus1            (CTLR *controller)
{
return (ddcmp_compare (controller->link.T, LT, controller->link.N + 1, controller));
}
t_bool ddcmp_TeqNplus1            (CTLR *controller)
{
return (ddcmp_compare (controller->link.T, EQ, controller->link.N + 1, controller));
}
t_bool ddcmp_ReceiveMessageError  (CTLR *controller)
{
if ((controller->link.ScanningEvents & DDCMP_EVENT_PKT_RCVD) && controller->link.rcv_pkt) {
    if (0 != ddcmp_crc16 (0, controller->link.rcv_pkt, 8)) {
        controller->link.nak_crc_reason = 1;    /* Header CRC Error */
        return TRUE;
        }
    if ((controller->link.rcv_pkt[0] != DDCMP_ENQ) &&
        (0 != ddcmp_crc16 (0, controller->link.rcv_pkt+8, controller->link.rcv_pkt_size-8))) {
        controller->link.nak_crc_reason = 2;    /* Data CRC Error */
        return TRUE;
        }
    controller->link.nak_crc_reason = 0;        /* No CRC Error */
    }
return FALSE;
}
t_bool ddcmp_NumEqR               (CTLR *controller)
{
return (controller->link.rcv_pkt &&
        ddcmp_compare (controller->link.rcv_pkt[DDCMP_NUM_OFFSET], EQ, controller->link.R, controller));
}
t_bool ddcmp_NumNeR               (CTLR *controller)
{
return (controller->link.rcv_pkt &&
        ddcmp_compare (controller->link.rcv_pkt[DDCMP_NUM_OFFSET], NE, controller->link.R, controller));
}
t_bool ddcmp_TransmitterIdle      (CTLR *controller)
{
return (NULL == controller->link.xmt_buffer);
}
t_bool ddcmp_TramsmitterBusy      (CTLR *controller)
{
return !ddcmp_TransmitterIdle(controller);
}
t_bool ddcmp_SACKisSet            (CTLR *controller)
{
return (controller->link.SACK);
}
t_bool ddcmp_SACKisClear          (CTLR *controller)
{
return (!(controller->link.SACK));
}
t_bool ddcmp_SNAKisSet            (CTLR *controller)
{
return (controller->link.SNAK);
}
t_bool ddcmp_SNAKisClear          (CTLR *controller)
{
return (!(controller->link.SNAK));
}
t_bool ddcmp_SREPisSet            (CTLR *controller)
{
return (controller->link.SREP);
}
t_bool ddcmp_SREPisClear          (CTLR *controller)
{
return (!(controller->link.SREP));
}
t_bool ddcmp_UserSendMessage      (CTLR *controller)
{
BUFFER *buffer = dmc_buffer_queue_head(controller->xmt_queue);

return (buffer && (buffer->transfer_buffer[0] == 0));
}
t_bool ddcmp_LineConnected        (CTLR *controller)
{
return (*controller->modem & DMC_SEL4_M_CAR);
}
t_bool ddcmp_LineDisconnected     (CTLR *controller)
{
return (!(*controller->modem & DMC_SEL4_M_CAR));
}
t_bool ddcmp_DataMessageSent      (CTLR *controller)
{
t_bool breturn = ((controller->link.ScanningEvents & DDCMP_EVENT_XMIT_DONE) &&
                    controller->link.xmt_done_buffer && 
                    (controller->link.xmt_done_buffer->transfer_buffer[0] == DDCMP_SOH));
if (breturn)
    return breturn;
return breturn;
}
t_bool ddcmp_REPMessageSent       (CTLR *controller)
{
return ((controller->link.ScanningEvents & DDCMP_EVENT_XMIT_DONE) &&
        controller->link.xmt_done_buffer && 
        (controller->link.xmt_done_buffer->transfer_buffer[0] == DDCMP_ENQ) && 
        (controller->link.xmt_done_buffer->transfer_buffer[1] == DDCMP_CTL_REP));
}

void ddcmp_dispatch(CTLR *controller, uint32 EventMask)
{
DDCMP_STATETABLE *table;
int matched = 0;
static const char *states[] = {"Halt", "IStart", "AStart", "Run", "Maintenance", "All"};

if (controller->link.Scanning) {
    if (!controller->link.RecurseScan) {
        controller->link.RecurseScan = TRUE;
        controller->link.RecurseEventMask |= EventMask;
        }
    return;
    }
controller->link.Scanning = TRUE;
controller->link.ScanningEvents |= EventMask;
for (table=DDCMP_TABLE; table->Conditions[0] != NULL; ++table) {
    if ((table->State == controller->link.state) ||
        (table->State == All)) {
        t_bool match = TRUE;
        DDCMP_Condition_Routine *cond = table->Conditions;
        DDCMP_LinkAction_Routine *action = table->Actions;

        while (match && *cond != NULL) {
            match = (*cond)(controller);
            ++cond;
            }
        if (!match)
            continue;
        ++matched;
        sim_debug (DBG_INF, controller->device, "%s%d: ddcmp_dispatch(%X) - %s conditions matching for rule %2d(%s)%s,\n"
                                                "                                           initiating actions (%s)\n", controller->device->name, controller->index, EventMask, states[table->State], table->RuleNumber, ddcmp_conditions(table->Conditions), ddcmp_link_state(&controller->link), ddcmp_actions(table->Actions));
        while (*action != NULL) {
            (*action)(controller);
            ++action;
            }
        if (table->NewState != controller->link.state) {
            sim_debug (DBG_INF, controller->device, "%s%d: ddcmp_dispatch(%X) - state changing from %s to %s\n", controller->device->name, controller->index, EventMask, states[controller->link.state], states[table->NewState]);
            controller->link.state = table->NewState;
            }
        }
    }
if (matched) {
    sim_debug (DBG_INF, controller->device, "%s%d: ddcmp_dispatch(%X) - queues: %s\n", controller->device->name, controller->index, EventMask, controller_queue_state(controller));
    }
controller->link.Scanning = FALSE;
controller->link.ScanningEvents &= ~EventMask;
if (controller->link.RecurseScan) {
    controller->link.RecurseScan = FALSE;
    EventMask = controller->link.RecurseEventMask;
    controller->link.RecurseEventMask = 0;
    ddcmp_dispatch (controller, EventMask);
    dmc_svc (controller->unit);
    }
}


void dmc_ddcmp_start_transmitter (CTLR *controller)
{
BUFFER *buffer = dmc_buffer_queue_head(controller->xmt_queue);
t_stat r;

if ((controller->link.xmt_buffer) ||        /* if Already Transmitting */
    (!buffer) ||                            /*    or nothing pending */
    (!controller->line->conn))              /*    or not connected */
    return;                                 /* Do nothing */
while (buffer) {
    if (buffer->transfer_buffer[0] == 0)
        return;
    if ((controller->link.state != Maintenance) && (buffer->transfer_buffer[DDCMP_RESP_OFFSET] != controller->link.R)) {
        sim_debug(DBG_INF, controller->device, "%s%d: Packet RESP fixup from %d to %d %s\n", controller->device->name, controller->index, buffer->transfer_buffer[DDCMP_RESP_OFFSET], controller->link.R, controller_queue_state(controller));
        buffer->transfer_buffer[DDCMP_RESP_OFFSET] = controller->link.R; /* Make sure that ACK or implied ACK is always up to date */
        }
    /* Need to make sure we dynamically compute the packet CRCs since header details can change */
    r = ddcmp_tmxr_put_packet_crc_ln (controller->line, buffer->transfer_buffer, buffer->count, *controller->corruption_factor);
    if (r == SCPE_OK) {
        controller->link.xmt_buffer = buffer;
        controller->ddcmp_control_packets_sent += (buffer->transfer_buffer[0] == DDCMP_ENQ) ? 1 : 0;
        if (controller->byte_wait) {  /* Speed limited? */
            buffer->buffer_return_time = buffer->count*controller->byte_wait + sim_grtime();
            sim_activate (controller->unit, buffer->count*controller->byte_wait);
            break;
            }
        dmc_complete_transmit (controller);
        buffer = dmc_buffer_queue_head(controller->xmt_queue);
        continue;
        }
    break;
    }
}

void dmc_check_romi(CTLR *controller)
{
if (dmc_is_dmc (controller) &&
    (*controller->csrs->sel0 & DMC_SEL0_M_ROMI) &&
    (*controller->csrs->sel0 & (DMC_SEL0_M_STEPUP | DMC_SEL0_M_RUN))) {
    /* DMC-11 or DMR-11, with ROMI and either STEP or RUN bits set.  */
    switch (*controller->csrs->sel6) {
        case DSPDSR:    /* 0x22b3 (read line status instruction), set the DSR and high speed bits in SEL2.  */
            sim_debug(DBG_TRC, controller->device, "%s%d: dmc_check_romi(). report DSR\n", controller->device->name, controller->index);
            dmc_setreg (controller, 2, 0xa00, DBG_RGC);
            break;
        case DROPDTR:   /* 0xa40b (drop DTR instruction) - VMS Driver uses this  */
            sim_debug(DBG_TRC, controller->device, "%s%d: dmc_check_romi(). drop_DTR\n", controller->device->name, controller->index);
            dmc_clr_modem_dtr (controller);
            break;
        case UINST_CNF3: /* 0x2293 (config switches to BSEL3) RSTS uses this */
            sim_debug(DBG_TRC, controller->device, "%s%d: dmc_check_romi(). report config\n", controller->device->name, controller->index);
            dmc_setreg (controller, 2, 0x0600, DBG_RGC); /* 1 Mb, DMR */
            break;
        case UINST_CNF: /* 0x2296 (get configuration switches) - VMS Driver uses this  */
            sim_debug(DBG_TRC, controller->device, "%s%d: dmc_check_romi(). report config\n", controller->device->name, controller->index);
            dmc_setreg (controller, 6, 0x0006, DBG_RGC); /* 1 Mb, DMR */
            break;
        default:
            if ((*controller->csrs->sel6 & 0xff00) == UINST_RROM) {
                /* Read ROM: VMS and RSTS do this */
                sim_debug(DBG_TRC, controller->device, "%s%d: dmc_check_romi(). read rom[0x%02x]\n", controller->device->name, controller->index, *controller->csrs->sel6 & 0xff);
                dmc_setreg (controller, 6,
                            hi_speed_ucode[*controller->csrs->sel6 & 0xff],
                            DBG_RGC);
                }
            else
                sim_debug(DBG_WRN, controller->device, "%s%d: dmc_check_romi(). Unknown Microcode instruction 0x%04x\n", controller->device->name, controller->index, *controller->csrs->sel6);
            break;
        }
    /* If it was a STEP we do this only once. */
    *controller->csrs->sel0 &= ~DMC_SEL0_M_STEPUP;
    controller->transfer_state = Idle;
    }
}

void dmc_process_immediate(CTLR *controller)
{
if (dmc_is_master_clear_set(controller)) {
    dmc_process_master_clear(controller);
    return;
    }
    dmc_check_romi(controller);             /* Do any needed immediate actions */
}

void dmc_process_command(CTLR *controller)
{
if (controller->transfer_state == InputTransfer) {
    dmc_process_input_transfer_completion(controller);
    return;
    }
if (controller->transfer_state == OutputTransfer) {
    dmc_check_for_output_transfer_completion(controller);
    return;
    }
if (controller->transfer_state == OutputControl) {
    dmc_check_for_output_control_completion(controller);
    return;
    }
/* transfer_state Idle */
if (dmc_is_rqi_set(controller)) {
    dmc_start_input_transfer(controller);
    return;
    }
if (!dmc_is_dmc (controller) ||
    (*controller->csrs->sel0 & DMC_SEL0_M_ROMI) == 0) {
    if (dmc_is_run_set(controller)) {
        dmc_start_control_output_transfer(controller);
        dmc_start_transfer_buffer(controller);
        }
    }
if (tmxr_get_line_loopback (controller->line) ^ dmc_is_lu_loop_set (controller)) {
    sim_debug(DBG_INF, controller->device, "%s%d: %s loopback mode\n", controller->device->name, controller->index, dmc_is_lu_loop_set (controller) ? "Enabling" : "Disabling");
    tmxr_set_line_loopback (controller->line, dmc_is_lu_loop_set (controller));
    }
}

t_stat dmc_rd(int32 *data, int32 PA, int32 access)
{
CTLR *controller = dmc_get_controller_from_address(PA);
int reg = PA & ((UNIBUS) ? 07 : 017);

*data = dmc_getreg(controller, PA, DBG_REG);
if (access == READ) {
    sim_debug(DBG_REG, controller->device, "dmc_rd(%s%d), addr=0x%08x, SEL%d, data=0x%04x\n", controller->device->name, controller->index, PA, reg, *data);
    }
else {
    sim_debug(DBG_REG, controller->device, "dmc_rd(%s%d), addr=0x%08x, BSEL%d, data=%02x\n", controller->device->name, controller->index, PA, reg, *data & 0xFF);
    }

return SCPE_OK;
}

t_stat dmc_wr(int32 data, int32 PA, int32 access)
{
CTLR *controller = dmc_get_controller_from_address(PA);
int reg = PA & ((UNIBUS) ? 07 : 017);
uint16 oldValue = dmc_getreg(controller, PA, 0);

if (access == WRITE) {
    sim_debug(DBG_REG, controller->device, "dmc_wr(%s%d), addr=0x%08x, SEL%d, newdata=0x%04x, olddata=0x%04x\n", controller->device->name, controller->index, PA, reg, data, oldValue);
    }
else {
    sim_debug(DBG_REG, controller->device, "dmc_wr(%s%d), addr=0x%08x, BSEL%d, newdata=%02x, olddata=0x%02x\n", controller->device->name, controller->index, PA, reg, data & 0xFF, ((PA&1) ? oldValue>>8 : oldValue) & 0xFF);
    }

if (access == WRITE) {
    if (PA & 1) {
        sim_debug(DBG_WRN, controller->device, "dmc_wr(%s%d), Unexpected non-16-bit write access to SEL%d\n", controller->device->name, controller->index, reg);
        }
    dmc_setreg(controller, PA, (uint16)data, DBG_REG);
    }
else {
    uint16 mask;
    if (PA & 1) {
        mask = 0xFF00;
        data = data << 8;
        }
    else {
        mask = 0x00FF;
        }

    dmc_setreg(controller, PA, (oldValue & ~mask) | (data & mask), DBG_REG);
    }

if (dmc_getsel(reg) == 0)    /* writes to SEL0 may need immediate action */
    dmc_process_immediate(controller);      /* Do any needed immediate actions */
if ((dmc_getsel(reg) == 0) || (dmc_getsel(reg) == 1)) {/* writes to SEL0 and SEL2 are actionable */
    if (0 == controller->dmc_wr_delay) {    /* Not waiting? */
        controller->dmc_wr_delay = 10;      /* Wait a bit before acting on the changed register */
        sim_activate_abs (controller->unit, controller->dmc_wr_delay);
        }
    }

return SCPE_OK;
}

int32 dmc_ininta (void)
{
int i;
int32 ans = 0; /* no interrupt request active */

for (i=0; i<DMC_NUMDEVICE+DMP_NUMDEVICE; i++) {
    CTLR *controller = &dmc_ctrls[i];
    if (controller->in_int != 0) {
        DIB *dib = (DIB *)controller->device->ctxt;
        ans = dib->vec + (8 * (int)(controller->unit - controller->device->units));
        dmc_clrinint(controller);
        sim_debug(DBG_INT, controller->device, "RXINTA Device %d - Vector: 0x%x(0%3o)\n", (int)(controller->unit-controller->device->units), ans, ans);
        break;
        }
    }

return ans;
}

int32 dmc_outinta (void)
{
int i;
int32 ans = 0; /* no interrupt request active */

for (i=0; i<DMC_NUMDEVICE+DMP_NUMDEVICE; i++) {
    CTLR *controller = &dmc_ctrls[i];
    if (controller->out_int != 0) {
        DIB *dib = (DIB *)controller->device->ctxt;
        ans = dib->vec + 4 + (8 * (int)(controller->unit - controller->device->units));
        dmc_clroutint(controller);
        sim_debug(DBG_INT, controller->device, "TXINTA Device %d - Vector: 0x%x(0%3o)\n", (int)(controller->unit-controller->device->units), ans, ans);
        break;
        }
    }

return ans;
}

t_stat dmc_reset (DEVICE *dptr)
{
t_stat ans = SCPE_OK;
CTLR *controller;
uint32 i, j;

sim_debug(DBG_TRC, dptr, "dmc_reset(%s)\n", dptr->name);

dmc_desc.packet = TRUE;
dmc_desc.buffered = 16384;
dmp_desc.packet = TRUE;
dmp_desc.buffered = 16384;
/* Connect structures together */
for (i=0; i < DMC_NUMDEVICE; i++) {
    dmc_csrs[i].sel0 = &dmc_sel0[i];
    dmc_csrs[i].sel2 = &dmc_sel2[i];
    dmc_csrs[i].sel4 = &dmc_sel4[i];
    dmc_csrs[i].sel6 = &dmc_sel6[i];
    controller = &dmc_ctrls[i];
    controller->csrs = &dmc_csrs[i];
    controller->line = &dmc_desc.ldsc[i];
    controller->rcv_queue = &dmc_rcv_queues[i];
    controller->completion_queue = &dmc_completion_queues[i];
    controller->xmt_queue = &dmc_xmt_queues[i];
    controller->ack_wait_queue = &dmc_ack_wait_queues[i];
    controller->free_queue = &dmc_free_queues[i];
    controller->buffers = &dmc_buffers[i];
    controller->device = &dmc_dev;
    controller->baseaddr = &dmc_baseaddr[i];
    controller->basesize = &dmc_basesize[i];
    controller->modem = &dmc_modem[i];
    controller->corruption_factor = &dmc_corruption[i];
    controller->unit = &controller->device->units[i];
    controller->unit->ctlr = (void *)controller;
    controller->index = i;
    }
tmxr_connection_poll_interval (&dmc_desc, dmc_connect_poll);
for (i=0; i < DMP_NUMDEVICE; i++) {
    dmp_csrs[i].sel0 = &dmp_sel0[i];
    dmp_csrs[i].sel2 = &dmp_sel2[i];
    dmp_csrs[i].sel4 = &dmp_sel4[i];
    dmp_csrs[i].sel6 = &dmp_sel6[i];
    dmp_csrs[i].sel10 = &dmp_sel10[i];
    controller = &dmc_ctrls[i+DMC_NUMDEVICE];
    controller->csrs = &dmp_csrs[i];
    controller->line = &dmp_desc.ldsc[i];
    controller->rcv_queue = &dmp_rcv_queues[i];
    controller->completion_queue = &dmp_completion_queues[i];
    controller->xmt_queue = &dmp_xmt_queues[i];
    controller->ack_wait_queue = &dmp_ack_wait_queues[i];
    controller->free_queue = &dmp_free_queues[i];
    controller->buffers = &dmp_buffers[i];
    controller->device = (UNIBUS) ? &dmp_dev : &dmv_dev;
    controller->dev_type = DMP;
    controller->baseaddr = &dmp_baseaddr[i];
    controller->basesize = &dmp_basesize[i];
    controller->modem = &dmp_modem[i];
    controller->corruption_factor = &dmp_corruption[i];
    controller->unit = &controller->device->units[i];
    controller->unit->ctlr = (void *)controller;
    controller->index = i + DMC_NUMDEVICE;
    }
tmxr_connection_poll_interval (&dmp_desc, dmp_connect_poll);
if (0 == dmc_units[0].flags) {       /* First Time Initializations */
    for (i=0; i < DMC_NUMDEVICE; i++) {
        controller = &dmc_ctrls[i];
        controller->state = Uninitialised;
        controller->transfer_state = Idle;
        controller->control_out = NULL;
        *controller->modem = 0;
#if defined (VM_PDP10)
        controller->dev_type = DMR;
#else
        controller->dev_type = DMC;
#endif
        dmc_dev.units[i] = dmc_unit_template;
        controller->unit->ctlr = (void *)controller;
        dmc_microdiag[i] = TRUE;
        dmc_corruption[i] = 0;
        }
    tmxr_set_modem_control_passthru (&dmc_desc);   /* We always want Modem Control */
    dmc_units[dmc_dev.numunits-2] = dmc_poll_unit_template;
    dmc_units[dmc_dev.numunits-2].ctlr = dmc_units[0].ctlr;
    dmc_units[dmc_dev.numunits-1] = dmc_timer_unit_template;
    dmc_units[dmc_dev.numunits-1].ctlr = dmc_units[0].ctlr;
    dmc_desc.notelnet = TRUE;                      /* We always want raw tcp socket */
    dmc_desc.dptr = &dmc_dev;                      /* Connect appropriate device */
    dmc_desc.uptr = dmc_units+dmc_desc.lines;      /* Identify polling unit */
    for (i=0; i < DMP_NUMDEVICE; i++) {
        controller = &dmc_ctrls[i+DMC_NUMDEVICE];
        controller->state = Uninitialised;
        controller->transfer_state = Idle;
        controller->control_out = NULL;
        *controller->modem = 0;
        dmp_dev.units[i] = dmc_unit_template;
        controller->unit->ctlr = (void *)controller;
        dmp_corruption[i] = 0;
        }
    tmxr_set_modem_control_passthru (&dmp_desc);   /* We always want Modem Control */
    dmp_units[dmp_dev.numunits-2] = dmc_poll_unit_template;
    dmp_units[dmp_dev.numunits-2].ctlr = dmp_units[0].ctlr;
    dmp_units[dmp_dev.numunits-1] = dmc_timer_unit_template;
    dmp_units[dmp_dev.numunits-1].ctlr = dmp_units[0].ctlr;
    dmp_desc.notelnet = TRUE;                      /* We always want raw tcp socket */
    dmp_desc.dptr = &dmp_dev;                      /* Connect appropriate device */
    dmp_desc.uptr = dmp_units+dmp_desc.lines;      /* Identify polling unit */
    }

ans = auto_config (dptr->name, (dptr->flags & DEV_DIS) ? 0 : dptr->numunits - 2);

if (!(dptr->flags & DEV_DIS)) {
    int32 attached = 0;

    for (i = 0; i < DMC_NUMDEVICE + DMP_NUMDEVICE; i++) {
        if (dmc_ctrls[i].device == dptr) {
            BUFFER *buffer;

            controller = &dmc_ctrls[i];
            /* Avoid memory leaks by moving all buffers back to the free queue
               and then freeing any allocated transfer buffers for each buffer
               on the free queue */
            while (controller->ack_wait_queue->count) {
                buffer = (BUFFER *)remqueue (controller->ack_wait_queue->hdr.next);
                ASSURE (insqueue (&buffer->hdr, controller->free_queue->hdr.prev));
                }
            while (controller->completion_queue->count) {
                buffer = (BUFFER *)remqueue (controller->completion_queue->hdr.next);
                ASSURE (insqueue (&buffer->hdr, controller->free_queue->hdr.prev));
                }
            while (controller->rcv_queue->count) {
                buffer = (BUFFER *)remqueue (controller->rcv_queue->hdr.next);
                ASSURE (insqueue (&buffer->hdr, controller->free_queue->hdr.prev));
                }
            while (controller->xmt_queue->count) {
                buffer = (BUFFER *)remqueue (controller->xmt_queue->hdr.next);
                ASSURE (insqueue (&buffer->hdr, controller->free_queue->hdr.prev));
                }
            for (j = 0; j < controller->free_queue->size; j++) {
                buffer = (BUFFER *)remqueue (controller->free_queue->hdr.next);
                free (buffer->transfer_buffer);
                buffer->transfer_buffer = NULL;
                ASSURE (insqueue (&buffer->hdr, controller->free_queue->hdr.prev));
                }
            dmc_buffer_queue_init_all(controller);
            dmc_clrinint(controller);
            dmc_clroutint(controller);
            controller->dmc_wr_delay = 0;
            for (j=0; j<dptr->numunits-1; j++) {
                sim_cancel (&dptr->units[j]); /* stop poll */
                if (dptr->units[j].flags & UNIT_ATT)
                    ++attached;
                }
            dmc_process_master_clear(controller);
            }
        }
    if (attached)
        sim_activate_after (dptr->units+(dptr->numunits-2), DMC_CONNECT_POLL*1000000);/* start poll */
    }

return ans;
}

t_stat dmc_attach (UNIT *uptr, CONST char *cptr)
{
DEVICE *dptr = (UNIBUS) ? ((&dmc_dev == find_dev_from_unit(uptr)) ? &dmc_dev : &dmp_dev) : &dmv_dev;
int32 dmc = (int32)(uptr-dptr->units);
TMXR *mp = (dptr == &dmc_dev) ? &dmc_desc : &dmp_desc;
t_stat ans = SCPE_OK;
char *peer = ((dptr == &dmc_dev)? &dmc_peer[dmc][0] : &dmp_peer[dmc][0]);
char *port = ((dptr == &dmc_dev)? &dmc_port[dmc][0] : &dmp_port[dmc][0]);
char attach_string[1024];

if (!cptr || !*cptr)
    return SCPE_ARG;
if (!(uptr->flags & UNIT_ATTABLE))
    return SCPE_NOATT;
if (0 == strncasecmp (cptr, "SYNC", 4)) {
    sprintf (attach_string, "Line=%d,%s", dmc, cptr);
    ans = tmxr_open_master (mp, attach_string);
}
else {
    if (!peer[0]) {
        sim_printf ("Peer must be specified before attach\n");
        return SCPE_ARG;
    }
    sprintf (attach_string, "Line=%d,Connect=%s,%s", dmc, peer, cptr);
    ans = tmxr_open_master (mp, attach_string);                 /* open master socket */
}
if (ans != SCPE_OK)
    return ans;
mp->dptr = dptr;
strncpy (port, cptr, sizeof(dmc_port[0]));
uptr->filename = (char *)malloc (strlen(port)+1);
strcpy (uptr->filename, port);
uptr->flags |= UNIT_ATT;
sim_activate_after (dptr->units+(dptr->numunits-2), DMC_CONNECT_POLL*1000000);/* start poll */
return ans;
}

t_stat dmc_detach (UNIT *uptr)
{
DEVICE *dptr = (UNIBUS) ? ((&dmc_dev == find_dev_from_unit(uptr)) ? &dmc_dev : &dmp_dev) : &dmv_dev;
int32 dmc = (int32)(uptr-dptr->units);
TMXR *mp = (dptr == &dmc_dev) ? &dmc_desc : &dmp_desc;
TMLN *lp = &mp->ldsc[dmc];
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<mp->lines; i++)
    if (dptr->units[i].flags & UNIT_ATT)
        ++attached;
if (!attached) {
    sim_cancel (dptr->units+mp->lines);              /* stop poll on last detach */
    sim_cancel (dptr->units+(mp->lines+1));          /* stop timer on last detach */
    }
r = tmxr_detach_ln (lp);
free (uptr->filename);
uptr->filename = NULL;
return r;
}

const char *dmc_description (DEVICE *dptr)
{
#if defined (VM_PDP10)
return "DMR11 Synchronous network controller";
#else
return "DMC11 Synchronous network controller";
#endif
}

const char *dmp_description (DEVICE *dptr)
{
return (UNIBUS) ? "DMP11 Synchronous network controller"
                : "DMV11 Synchronous network controller";
}

