3111 lines
111 KiB
C
3111 lines
111 KiB
C
/* pdp11_kmc.c: KMC11-A with COMM IOP-DUP microcode Emulation
|
|
------------------------------------------------------------------------------
|
|
|
|
|
|
Initial implementation 2002 by Johnny Eriksson <bygg@stacken.kth.se>
|
|
|
|
Adapted to SIMH 3.? by Robert M. A. Jarratt in 2013
|
|
|
|
Completely rewritten by Timothe Litt <litt@acm.org> for SimH 4.0 in 2013.
|
|
Implements all DDCMP functions, although the DUP11 doesn't currently support half-duplex.
|
|
|
|
DUP separated into separate devices, the DUPs were created by Mark Pizzolato.
|
|
|
|
This code is Copyright 2002, 2013 by the authors,all rights reserved.
|
|
It is licensed under the standard SimH license.
|
|
------------------------------------------------------------------------------
|
|
|
|
Modification history:
|
|
|
|
05-Jun-13 TL Massive rewrite to split KMC/DUP, add missing functions, and
|
|
restructure so TOPS-10/20 & RSX are happy. Support multiple
|
|
KMCs.
|
|
14-Apr-13 RJ Took original sources into latest source code.
|
|
15-Feb-02 JE Massive changes/cleanups.
|
|
23-Jan-02 JE Modify for version 2.9.
|
|
17-Jan-02 JE First attempt.
|
|
------------------------------------------------------------------------------*/
|
|
/*
|
|
* Loose ends, known problems etc:
|
|
*
|
|
* This implementation should do both full and half duplex DDCMP, but half duplex needs to be tested.
|
|
*
|
|
* DUP: Unexplained code to generate SYNC and RCVEN based on RTS transitions
|
|
* prevents RTS from working; using hacked version.
|
|
* Select final speed limit.
|
|
*/
|
|
|
|
#if defined (VM_PDP10) /* PDP10 version */
|
|
#include "pdp10_defs.h"
|
|
|
|
#elif defined (VM_VAX) /* VAX version */
|
|
#include "vax_defs.h"
|
|
|
|
#else /* PDP-11 version */
|
|
#include "pdp11_defs.h"
|
|
#endif
|
|
|
|
#define KMC_RDX 8
|
|
|
|
#include "pdp11_dup.h"
|
|
#include "pdp11_ddcmp.h"
|
|
|
|
#define DIM(x) (sizeof(x)/sizeof((x)[0]))
|
|
#define UNUSED_ARG(x) (void)(x)
|
|
|
|
/* Configuration macros
|
|
*/
|
|
|
|
/* From host VM: DUP_LINES is the maximum number of DUP lines on the Unibus.
|
|
* The KMC may not control all of them, but the DUP API also uses the index in
|
|
* IO space to identify the line. Default to max KDP supported.
|
|
*
|
|
* Note: It is perfectly reasonable to have MANY more DUP_LINES than a single KMC
|
|
* can support. A configuration can have multiple KMCs, or DUPs that are controlled
|
|
* directly by the OS. DUP_LINES allocates space for the KMC to keep track of lines
|
|
* that some KMC instance can POTENTIALLY control.
|
|
*/
|
|
#ifndef DUP_LINES
|
|
#define DUP_LINES (MAX_LINE +1)
|
|
#endif
|
|
|
|
/* Number of KMC devices possible. */
|
|
#ifndef KMC_UNITS
|
|
#define KMC_UNITS 1
|
|
#endif
|
|
|
|
/* Number of KMC devices initially enabled. */
|
|
#ifndef INITIAL_KMCS
|
|
#define INITIAL_KMCS 1
|
|
#endif
|
|
|
|
#if INITIAL_KMCS > KMC_UNITS
|
|
#undef INITIAL_KMCS
|
|
#define INITIAL_KMCS KMC_UNITS
|
|
#endif
|
|
|
|
#if INITIAL_KMCS == 0
|
|
#undef INITIAL_KMCS
|
|
#define INITIAL_KMCS 1
|
|
#define KMC_DIS DEV_DIS
|
|
#else
|
|
#define KMC_DIS 0
|
|
#endif
|
|
|
|
/* Define DUP_RXRESYNC to disable/enable RCVEN at the expected times.
|
|
* This is used to resynch the receiver, but I'm not sure if it would
|
|
* cause the emulated DUP to lose data...especially with QSYNC
|
|
#define DUP_RXRESYNC 1
|
|
*/
|
|
|
|
/* End of configuration */
|
|
|
|
/* Architectural structures and macros */
|
|
|
|
/* Maximum line speed
|
|
* KDP limit was about 19,200 BPS.
|
|
* Used to pace BD consumption. If too fast, too many messages can
|
|
* be outstanding and the OS will reject ACKs. *TODO* Pick a final limit.
|
|
*/
|
|
#define MAX_SPEED (1*1000*1000)
|
|
|
|
#define DFLT_SPEED (19200)
|
|
|
|
|
|
/* Transmission (or reception) time of a buffer of n
|
|
* characters at speed bits/sec. 8 bit chars (sync line)
|
|
* result in microseconds.
|
|
*/
|
|
#define XTIME(n,speed) (((n)*8*1000*1000)/(speed))
|
|
|
|
/* 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;
|
|
};
|
|
typedef struct queuehdr QH;
|
|
|
|
/* bits, SEL0 */
|
|
|
|
#define SEL0_RUN 0100000 /* Run bit. */
|
|
#define SEL0_MRC 0040000 /* Master clear. */
|
|
#define SEL0_CWR 0020000 /* CRAM write. */
|
|
#define SEL0_SLU 0010000 /* Step Line Unit. */
|
|
#define SEL0_LUL 0004000 /* Line Unit Loop. */
|
|
#define SEL0_RMO 0002000 /* ROM output. */
|
|
#define SEL0_RMI 0001000 /* ROM input. */
|
|
#define SEL0_SUP 0000400 /* Step microprocessor. */
|
|
#define SEL0_RQI 0000200 /* Request input. */
|
|
#define SEL0_IEO 0000020 /* Interrupt enable output. */
|
|
#define SEL0_IEI 0000001 /* Interrupt enable input. */
|
|
|
|
/* bits, SEL2 */
|
|
|
|
#define SEL2_OVR 0100000 /* Completion queue overrun. */
|
|
#define SEL2_V_LINE 8 /* Line number assigned by host */
|
|
#define SEL2_LINE (0177 << SEL2_V_LINE)
|
|
#define MAX_LINE 017 /* Maximum line number allowed in BASE_IN */
|
|
#define MAX_ACTIVE (MAX_LINE+1)
|
|
#define UNASSIGNED_LINE (MAX_ACTIVE+1)
|
|
#define SEL2_RDO 0000200 /* Ready for output transaction. */
|
|
#define SEL2_RDI 0000020 /* Ready for input transaction. */
|
|
#define SEL2_IOT 0000004 /* I/O type, 1 = rx, 0 = tx. */
|
|
#define SEL2_V_CMD 0 /* Command code */
|
|
#define SEL2_CMD 0000003 /* Command code
|
|
* IN are commands TO the KMC
|
|
* OUT are command completions FROM the KMC.
|
|
*/
|
|
# define CMD_BUFFIN 0 /* BUFFER IN */
|
|
# define CMD_CTRLIN 1 /* CONTROL IN */
|
|
# define CMD_BASEIN 3 /* BASE IN */
|
|
# define CMD_BUFFOUT 0 /* BUFFER OUT */
|
|
# define CMD_CTRLOUT 1 /* CONTROL OUT */
|
|
|
|
#define SEL2_II_RESERVED (SEL2_OVR | 0354) /* Reserved: 15, 7:5, 3:2 */
|
|
/* bits, SEL4 */
|
|
|
|
#define SEL4_CI_POLL 0377 /* DUP polling interval, 50 usec units */
|
|
|
|
#define SEL4_ADDR 0177777 /* Generic: Unibus address <15:0> */
|
|
|
|
/* bits, SEL6 */
|
|
|
|
#define SEL6_V_CO_XAD 14 /* Unibus extended address bits */
|
|
#define SEL6_CO_XAD (3u << SEL6_V_CO_XAD)
|
|
|
|
/* BASE IN */
|
|
#define SEL6_II_DUPCSR 0017770 /* BASE IN: DUP CSR <12:3> */
|
|
|
|
/* BUFFER IN */
|
|
#define SEL6_BI_ENABLE 0020000 /* BUFFER IN: Assign after KILL */
|
|
#define SEL6_BI_KILL 0010000 /* BUFFER IN: Return all buffers */
|
|
|
|
/* BUFFER OUT */
|
|
#define SEL6_BO_EOM 0010000 /* BUFFER OUT: End of message */
|
|
|
|
/* CONTROL OUT event codes */
|
|
#define SEL6_CO_ABORT 006 /* Bit stuffing rx abort */
|
|
#define SEL6_CO_HCRC 010 /* DDCMP Header CRC error */
|
|
#define SEL6_CO_DCRC 012 /* DDCMP Data CRC/ BS frame CRC */
|
|
#define SEL6_CO_NOBUF 014 /* No RX buffer available */
|
|
#define SEL6_CO_DSRCHG 016 /* DSR changed (Initially OFF) */
|
|
#define SEL6_CO_NXM 020 /* NXM */
|
|
#define SEL6_CO_TXU 022 /* Transmitter underrun */
|
|
#define SEL6_CO_RXO 024 /* Receiver overrun */
|
|
#define SEL6_CO_KDONE 026 /* Kill complete */
|
|
|
|
/* CONTROL IN modifiers */
|
|
#define SEL6_CI_V_DDCMP 15 /* Run DDCMP vs. bit-stuffing */
|
|
#define SEL6_CI_DDCMP (1u << SEL6_CI_V_DDCMP)
|
|
#define SEL6_CI_V_HDX 13 /* Half-duplex */
|
|
#define SEL6_CI_HDX (1u << SEL6_CI_V_HDX)
|
|
#define SEL6_CI_V_ENASS 12 /* Enable secondary station address filter */
|
|
#define SEL6_CI_ENASS (1u << SEL6_CI_V_ENASS)
|
|
#define SEL6_CI_V_NOCRC 9
|
|
#define SEL6_CI_NOCRC (1u << SEL6_CI_V_NOCRC)
|
|
#define SEL6_CI_V_ENABLE 8
|
|
#define SEL6_CI_ENABLE (1u << SEL6_CI_V_ENABLE)
|
|
#define SEL6_CI_SADDR 0377
|
|
|
|
/* Buffer descriptor list bits */
|
|
|
|
#define BDL_LDS 0100000 /* Last descriptor in list. */
|
|
#define BDL_RSY 0010000 /* Resync transmitter. */
|
|
#define BDL_XAD 0006000 /* Buffer address bits 17 & 16. */
|
|
#define BDL_S_XAD (16-10) /* Shift to position XAX in address */
|
|
#define BDL_EOM 0001000 /* End of message. */
|
|
#define BDL_SOM 0000400 /* Start of message. */
|
|
|
|
#define KMC_CRAMSIZE 1024 /* Size of CRAM (microcode control RAM). */
|
|
#define KMC_DRAMSIZE 1024
|
|
#define KMC_CYCLETIME 300 /* Microinstruction cycle time, nsec */
|
|
|
|
#define MAXQUEUE 2 /* Number of bdls that can be queued for tx and rx
|
|
* per line. (KDP ucode limits to 2)
|
|
*/
|
|
|
|
struct buffer_list { /* BDL queue elements */
|
|
QH hdr;
|
|
uint32 ba;
|
|
};
|
|
typedef struct buffer_list BDL;
|
|
|
|
struct workblock {
|
|
t_bool first;
|
|
uint32 bda;
|
|
uint16 bd[3];
|
|
uint16 rcvc;
|
|
uint32 ba;
|
|
};
|
|
typedef struct workblock WB;
|
|
|
|
/* Each DUP in the system can potentially be assigned to a KMC.
|
|
* Since the total number of DUPs is relatively small, and in
|
|
* most configurations all DUPs will be assigned, a dup structure
|
|
* is allocated for all possible DUPs. This structure is common
|
|
* to ALL KMCs; a given DUP is assigned to at most one.
|
|
*/
|
|
|
|
struct dupstate {
|
|
int32 kmc; /* Controlling KMC */
|
|
uint8 line; /* OS-assigned line number */
|
|
int32 dupidx; /* DUP API Number amongst all DUP11's on Unibus (-1 == unassigned) */
|
|
int32 linkstate; /* Line Link Status (i.e. 1 when DCD/DSR is on, 0 otherwise */
|
|
#define LINK_DSR 1
|
|
#define LINK_SEL 2
|
|
uint16 ctrlFlags;
|
|
uint32 dupcsr;
|
|
uint32 linespeed; /* Effective line speed (bps) */
|
|
BDL bdq[MAXQUEUE*2]; /* Queued TX and RX buffer lists */
|
|
QH bdqh; /* Free queue */
|
|
int32 bdavail;
|
|
|
|
QH rxqh; /* Receive queue from host */
|
|
int32 rxavail;
|
|
WB rx;
|
|
uint32 rxstate;
|
|
/* States. Note that these are ordered; there are < comparisions */
|
|
#define RXIDLE 0
|
|
#define RXBDL 1
|
|
#define RXBUF 2
|
|
#define RXDAT 3
|
|
#define RXLAST 4
|
|
#define RXFULL 5
|
|
#define RXNOBUF 6
|
|
|
|
uint8 *rxmsg;
|
|
uint16 rxmlen;
|
|
uint16 rxdlen;
|
|
uint16 rxused;
|
|
|
|
QH txqh; /* Transmit queue from host */
|
|
int32 txavail;
|
|
WB tx;
|
|
uint32 txstate;
|
|
/* States. Note that these are ordered; there are < comparisions */
|
|
#define TXIDLE 0
|
|
#define TXDONE 1
|
|
#define TXRTS 2
|
|
#define TXSOM 3
|
|
#define TXHDR 4
|
|
#define TXHDRX 5
|
|
#define TXDATA 6
|
|
#define TXDATAX 7
|
|
#define TXMRDY 8
|
|
#define TXRDY 9
|
|
#define TXACT 10
|
|
#define TXKILL 11
|
|
#define TXKILR 12
|
|
|
|
uint8 *txmsg;
|
|
size_t txmsize, txslen, txmlen;
|
|
};
|
|
|
|
typedef struct dupstate dupstate;
|
|
|
|
/* State for every DUP that MIGHT be controlled.
|
|
* A DUP can be controlled by at most one KMC.
|
|
*/
|
|
static dupstate dupState[DUP_LINES] = {{ 0 }};
|
|
|
|
/* Flags defining sim_debug conditions. */
|
|
|
|
#define DF_CMD 00001 /* Trace commands. */
|
|
#define DF_BFO 00002 /* Trace buffers out */
|
|
#define DF_CTO 00004 /* Trace control out */
|
|
#define DF_QUE 00010 /* Trace internal queues */
|
|
#define DF_RGR 00020 /* Register reads */
|
|
#define DF_RGW 00040 /* Register writes. */
|
|
#define DF_INF 00100 /* Info */
|
|
#define DF_ERR 00200 /* Error halts */
|
|
#define DF_PKT 00400 /* Errors in packet (use DUP pkt trace for pkt data */
|
|
#define DF_INT 01000 /* Interrupt delivery */
|
|
#define DF_BUF 02000 /* Buffer service */
|
|
|
|
static DEBTAB kmc_debug[] = {
|
|
{"CMD", DF_CMD},
|
|
{"BFO", DF_BFO},
|
|
{"CTO", DF_CTO},
|
|
{"QUE", DF_QUE},
|
|
{"RGR", DF_RGR},
|
|
{"RGW", DF_RGW},
|
|
{"INF", DF_INF},
|
|
{"ERR", DF_ERR},
|
|
{"PKT", DF_PKT},
|
|
{"BUF", DF_BUF},
|
|
{"INT", DF_INT},
|
|
{0}
|
|
};
|
|
|
|
/* These count the total pending interrupts for each vector
|
|
* across all KMCs.
|
|
*/
|
|
|
|
static int32 AintPending = 0;
|
|
static int32 BintPending = 0;
|
|
|
|
/* Per-KMC state */
|
|
|
|
/* To help make the code more readable, by convention the symbol 'k'
|
|
* is the number of the KMC that is the target of the current operation.
|
|
* The global state variables below have a #define of the short form
|
|
* of each name. Thus, instead of kmc_upc[kmcnum][j], write upc[j].
|
|
* For this to work, k, a uint32 must be in scope and valid.
|
|
*
|
|
* k can be found in several ways:
|
|
* k is the offset into any of the tables.
|
|
* Given a UNIT pointer k = txup->unit_kmc.
|
|
* The KMC assigned to control a DUP is stored it its dupstate.
|
|
* k = dupState[dupno]->kmc; (-1 if not controlled by any KMC)
|
|
* The DUP associated with a line is stored in line2dup.
|
|
* k = line2dup[line]->kmc
|
|
* From the DEVICE pointer, dptr->units lists all the UNITs, and the
|
|
* number of units is dptr->numunits.
|
|
* From a CSR address:
|
|
* k = (PA - dib.ba) / IOLN_KMC
|
|
*
|
|
* Note that the UNIT arrays are indexed [line][kmc], so pointer
|
|
* math is not the best way to find k. (TX line 0 is the public
|
|
* UNIT for each KMC.)
|
|
*
|
|
*/
|
|
|
|
/* Emulator error halt codes
|
|
* These are mostl error conditions that produce undefined
|
|
* results in the hardware. To help with debugging, unique
|
|
* codes are provided here.
|
|
*/
|
|
|
|
#define HALT_STOP 0 /* Run bit cleared */
|
|
#define HALT_MRC 1 /* Master clear */
|
|
#define HALT_BADRES 2 /* Resume without initialization */
|
|
#define HALT_LINE 3 /* Line number out of range */
|
|
#define HALT_BADCMD 4 /* Undefined command received */
|
|
#define HALT_BADCSR 5 /* BASE IN had non-zero MBZ */
|
|
#define HALT_RCVOVF 6 /* Too many receive buffers assigned */
|
|
#define HALT_MTRCV 7 /* Receive buffer descriptor has zero size */
|
|
#define HALT_XMTOVF 8 /* Too many transmit buffers assigned */
|
|
#define HALT_XSOM 9 /* Transmission didn't start with SOM */
|
|
#define HALT_XSOM2 10 /* Data buffer didn't start with SOM */
|
|
#define HALT_BADUC 11 /* No or unrecognized microcode loaded */
|
|
|
|
/* KMC event notifications are funneled through the small number of CSRs.
|
|
* Since the CSRs may not be available when an event happens, events are
|
|
* queued in these structures. An event is represented by the values to
|
|
* be exposed in BSEL2, BSEL4, and BSEL6.
|
|
*
|
|
* Queue overflow is signalled by setting the overflow bit in the entry
|
|
* at the tail of the completion queue at the time a new entry fails to
|
|
* be inserted. Note that the line number in that entry may not be
|
|
* the line whose event was lost. Effectively, that makes this a fatal
|
|
* error.
|
|
*
|
|
* The KMC microcode uses a queue depth of 29.
|
|
*/
|
|
|
|
#define CQUEUE_MAX (29)
|
|
|
|
struct cqueue {
|
|
QH hdr;
|
|
uint16 bsel2, bsel4, bsel6;
|
|
};
|
|
typedef struct cqueue CQ;
|
|
|
|
/* CSRs. These are known as SELn as words and BSELn as bytes */
|
|
static uint16 kmc_sel0[KMC_UNITS]; /* CSR0 - BSEL 1,0 */
|
|
#define sel0 kmc_sel0[k]
|
|
static uint16 kmc_sel2[KMC_UNITS]; /* CSR2 - BSEL 3,2 */
|
|
#define sel2 kmc_sel2[k]
|
|
static uint16 kmc_sel4[KMC_UNITS]; /* CSR4 - BSEL 5,4 */
|
|
#define sel4 kmc_sel4[k]
|
|
static uint16 kmc_sel6[KMC_UNITS]; /* CSR6 - BSEL 7,6 */
|
|
#define sel6 kmc_sel6[k]
|
|
|
|
/* Microprocessor state - subset exposed to the host */
|
|
static uint16 kmc_upc[KMC_UNITS]; /* Micro PC */
|
|
#define upc kmc_upc[k]
|
|
static uint16 kmc_mar[KMC_UNITS]; /* Micro Memory Address Register */
|
|
#define mar kmc_mar[k]
|
|
static uint16 kmc_mna[KMC_UNITS]; /* Maintenance Address Register */
|
|
#define mna kmc_mna[k]
|
|
static uint16 kmc_mni[KMC_UNITS]; /* Maintenance Instruction Register */
|
|
#define mni kmc_mni[k]
|
|
static uint16 kmc_ucode[KMC_UNITS][KMC_CRAMSIZE];
|
|
#define ucode kmc_ucode[k]
|
|
static uint16 kmc_dram[KMC_UNITS][KMC_DRAMSIZE];
|
|
#define dram kmc_dram[k]
|
|
|
|
static dupstate *kmc_line2dup[KMC_UNITS][MAX_ACTIVE];
|
|
#define line2dup kmc_line2dup[k]
|
|
|
|
/* General state booleans */
|
|
static int kmc_gflags[KMC_UNITS]; /* Miscellaneous gflags */
|
|
#define gflags kmc_gflags[k]
|
|
# define FLG_INIT 000001 /* Master clear has been done once.
|
|
* Data structures trustworthy.
|
|
*/
|
|
# define FLG_AINT 000002 /* Pending KMC "A" (INPUT) interrupt */
|
|
# define FLG_BINT 000004 /* Pending KMC "B" (OUTPUT) interrupt */
|
|
# define FLG_UCINI 000010 /* Ucode initialized, ucode structures and ints OK */
|
|
|
|
/* Completion queue elements, header and freelist */
|
|
static CQ kmc_cqueue[KMC_UNITS][CQUEUE_MAX];
|
|
#define cqueue kmc_cqueue[k]
|
|
|
|
static QH kmc_cqueueHead[KMC_UNITS];
|
|
#define cqueueHead kmc_cqueueHead[k]
|
|
static int32 kmc_cqueueCount[KMC_UNITS];
|
|
#define cqueueCount kmc_cqueueCount[k]
|
|
|
|
static QH kmc_freecqHead[KMC_UNITS];
|
|
#define freecqHead kmc_freecqHead[k]
|
|
static int32 kmc_freecqCount[KMC_UNITS];
|
|
#define freecqCount kmc_freecqCount[k]
|
|
|
|
/* *** End of per-KMC state *** */
|
|
|
|
/* Forward declarations: simulator interface */
|
|
|
|
|
|
static t_stat kmc_reset(DEVICE * dptr);
|
|
static t_stat kmc_readCsr(int32* data, int32 PA, int32 access);
|
|
static t_stat kmc_writeCsr(int32 data, int32 PA, int32 access);
|
|
static void kmc_doMicroinstruction (int32 k, uint16 instr);
|
|
static t_stat kmc_txService(UNIT * txup);
|
|
static t_stat kmc_rxService(UNIT * rxup);
|
|
|
|
#if KMC_UNITS > 1
|
|
static t_stat kmc_setDeviceCount (UNIT *txup, int32 val, CONST char *cptr, void *desc);
|
|
static t_stat kmc_showDeviceCount (FILE *st, UNIT *txup, int32 val, CONST void *desc);
|
|
#endif
|
|
static t_stat kmc_setLineSpeed (UNIT *txup, int32 val, CONST char *cptr, void *desc);
|
|
static t_stat kmc_showLineSpeed (FILE *st, UNIT *txup, int32 val, CONST void *desc);
|
|
static t_stat kmc_showStatus (FILE *st, UNIT *up, int32 v, CONST void *dp);
|
|
|
|
static t_stat kmc_help (FILE *st, DEVICE *dptr,
|
|
UNIT *uptr, int32 flag, const char *cptr);
|
|
static const char *kmc_description (DEVICE *dptr);
|
|
|
|
/* Global data */
|
|
|
|
extern int32 IREQ (HLVL);
|
|
extern int32 tmxr_poll; /* calibrated delay */
|
|
|
|
static int32 kmc_AintAck (void);
|
|
static int32 kmc_BintAck (void);
|
|
|
|
#define IOLN_KMC 010
|
|
|
|
static DIB kmc_dib = {
|
|
IOBA_AUTO, /* ba - Base address */
|
|
IOLN_KMC * INITIAL_KMCS, /* lnt - Length */
|
|
&kmc_readCsr, /* rd - read IO */
|
|
&kmc_writeCsr, /* wr - write IO */
|
|
2 * INITIAL_KMCS, /* vnum - number of Interrupt vectors */
|
|
IVCL (KMCA), /* vloc - vector locator */
|
|
VEC_AUTO, /* vec - auto */
|
|
{&kmc_AintAck, /* ack - iack routines */
|
|
&kmc_BintAck},
|
|
IOLN_KMC, /* IO space per unit */
|
|
};
|
|
|
|
/* One UNIT for each (possible) active line for transmit, and another for receive */
|
|
static UNIT tx_units[MAX_ACTIVE][KMC_UNITS]; /* Line 0 is primary unit. txup references */
|
|
#define unit_kmc u3
|
|
#define unit_line u4
|
|
#define unit_htime u5
|
|
|
|
/* Timers - in usec */
|
|
|
|
#define RXPOLL_DELAY (1*1000) /* Poll for a new message */
|
|
#define RXBDL_DELAY (10*1000) /* Grace period for host to supply a BDL */
|
|
#define RXNEWBD_DELAY (10) /* Delay changing descriptors */
|
|
#define RXSTART_DELAY (50) /* Host provided BDL to receive start */
|
|
|
|
|
|
static UNIT rx_units[MAX_ACTIVE][KMC_UNITS]; /* Secondary unit, used for RX. rxup references */
|
|
|
|
DEVICE kmc_int_rxdev = {
|
|
"KDP-RX", rx_units[0],
|
|
NULL, /* Register decode tables */
|
|
NULL, /* Modifier table */
|
|
INITIAL_KMCS, /* Number of units */
|
|
KMC_RDX, /* Address radix */
|
|
13, /* Address width: 18 - <17:13> are 1s, omits UBA */
|
|
1, /* Address increment */
|
|
KMC_RDX, /* Data radix */
|
|
8, /* Data width */
|
|
NULL, /* examine routine */
|
|
NULL, /* Deposit routine */
|
|
NULL, /* reset routine */
|
|
NULL, /* boot routine */
|
|
NULL, /* attach routine */
|
|
NULL, /* detach routine */
|
|
NULL, /* context */
|
|
DEV_NOSAVE, /* Flags */
|
|
0, /* debug control */
|
|
NULL, /* debug flag table */
|
|
NULL, /* memory size routine */
|
|
NULL, /* logical name */
|
|
NULL, /* help routine */
|
|
NULL, /* attach help routine */
|
|
NULL, /* help context */
|
|
&kmc_description /* Device description routine */
|
|
};
|
|
|
|
|
|
/* Timers - in usec */
|
|
|
|
#define TXSTART_DELAY (10) /* TX BUFFER IN to TX start */
|
|
#define TXDONE_DELAY (10) /* Completion to to poll for next bd */
|
|
#define TXCTS_DELAY (100*1000) /* Polling for CTS to appear */
|
|
#define TXDUP_DELAY (1*1000*1000) /* Wait for DUP to accept data (SNH) */
|
|
|
|
static BITFIELD kmc_sel0_decoder[] = {
|
|
BIT (IEI),
|
|
BITNCF (3),
|
|
BIT (IEO),
|
|
BIT (RQI),
|
|
BITNCF (2),
|
|
BIT (SUP),
|
|
BIT (RMI),
|
|
BIT (RMO),
|
|
BIT (LUL),
|
|
BIT (SLU),
|
|
BIT (CWR),
|
|
BIT (MRC),
|
|
BIT (RUN),
|
|
ENDBITS
|
|
};
|
|
static BITFIELD kmc_sel2_decoder[] = {
|
|
BITF (CMD,2),
|
|
BIT (IOT),
|
|
BITNCF (1),
|
|
BIT (RDI),
|
|
BITNCF (2),
|
|
BIT (RDO),
|
|
BITFFMT (LINE,7,"%u"),
|
|
BIT (CQOVF),
|
|
ENDBITS
|
|
};
|
|
static REG kmc_reg[] = {
|
|
{ BRDATADF (SEL0, kmc_sel0, KMC_RDX, 16, KMC_UNITS, "Initialization/control", kmc_sel0_decoder) },
|
|
{ BRDATADF (SEL2, kmc_sel2, KMC_RDX, 16, KMC_UNITS, "Command/line", kmc_sel2_decoder) },
|
|
{ ORDATA (SEL4, kmc_sel4, 16) },
|
|
{ ORDATA (SEL6, kmc_sel6, 16) },
|
|
{ NULL },
|
|
};
|
|
|
|
MTAB kmc_mod[] = {
|
|
{ MTAB_XTD|MTAB_VDV|MTAB_VALR, 010, "ADDRESS", "ADDRESS",
|
|
&set_addr, &show_addr, NULL, "Bus address" },
|
|
{ MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "VECTOR", "VECTOR",
|
|
&set_vec, &show_vec, NULL, "Interrupt vector" },
|
|
{ MTAB_XTD|MTAB_VDV|MTAB_VALR|MTAB_NMO, 0, "SPEED", "SPEED=dup=bps",
|
|
&kmc_setLineSpeed, &kmc_showLineSpeed, NULL, "Line speed (bps)" },
|
|
{ MTAB_XTD|MTAB_VUN|MTAB_NMO, 1, "STATUS", NULL, NULL, &kmc_showStatus, NULL, "Display KMC status" },
|
|
#if KMC_UNITS > 1
|
|
{ MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "DEVICES", "DEVICES=n",
|
|
&kmc_setDeviceCount, &kmc_showDeviceCount, NULL, "Display number of KMC devices enabled" },
|
|
#endif
|
|
{ 0 },
|
|
};
|
|
|
|
DEVICE kmc_dev = {
|
|
"KDP",
|
|
tx_units[0],
|
|
kmc_reg, /* Register decode tables */
|
|
kmc_mod, /* Modifier table */
|
|
INITIAL_KMCS, /* Number of units */
|
|
KMC_RDX, /* Address radix */
|
|
13, /* Address width: 18 - <17:13> are 1s, omits UBA */
|
|
1, /* Address increment */
|
|
KMC_RDX, /* Data radix */
|
|
8, /* Data width */
|
|
NULL, /* examine routine */
|
|
NULL, /* Deposit routine */
|
|
&kmc_reset, /* reset routine */
|
|
NULL, /* boot routine */
|
|
NULL, /* attach routine */
|
|
NULL, /* detach routine */
|
|
&kmc_dib, /* context */
|
|
DEV_UBUS | KMC_DIS /* Flags */
|
|
| DEV_DISABLE
|
|
| DEV_DEBUG,
|
|
0, /* debug control */
|
|
kmc_debug, /* debug flag table */
|
|
NULL, /* memory size routine */
|
|
NULL, /* logical name */
|
|
&kmc_help, /* help routine */
|
|
NULL, /* attach help routine */
|
|
NULL, /* help context */
|
|
&kmc_description /* Device description routine */
|
|
};
|
|
|
|
/* Forward declarations: not referenced in simulator data */
|
|
|
|
static void kmc_masterClear(int32 k);
|
|
static void kmc_startUcode (int32 k);
|
|
static void kmc_dispatchInputCmd(int32 k);
|
|
|
|
/* Control functions */
|
|
static void kmc_baseIn (int32 k, dupstate *d, uint16 cmdsel2, uint8 line);
|
|
static void kmc_ctrlIn (int32 k, dupstate *d, int line);
|
|
|
|
/* Receive functions */
|
|
void kmc_rxBufferIn(dupstate *d, int32 ba, uint16 sel6v);
|
|
static void kdp_receive(int32 dupidx, int count);
|
|
|
|
/* Transmit functions */
|
|
static void kmc_txBufferIn(dupstate *d, int32 ba, uint16 sel6v);
|
|
static void kmc_txComplete (int32 dupidx, int status);
|
|
static t_bool kmc_txNewBdl(dupstate *d);
|
|
static t_bool kmc_txNewBd(dupstate *d);
|
|
static t_bool kmc_txAppendBuffer(dupstate *d);
|
|
|
|
/* Completions */
|
|
static void kmc_processCompletions (int32 k);
|
|
|
|
static void kmc_ctrlOut (int32 k, uint8 code, uint16 rx, uint8 line, uint32 bda);
|
|
static void kmc_modemChange (int32 dupidx);
|
|
static t_bool kmc_updateDSR (dupstate *d);
|
|
|
|
static t_bool kmc_bufferAddressOut (int32 k, uint16 flags, uint16 rx, uint8 line, uint32 bda);
|
|
|
|
/* Buffer descriptor list utilities */
|
|
static int32 kmc_updateBDCount(uint32 bda, uint16 *bd);
|
|
|
|
/* Errors */
|
|
static void kmc_halt (int32 k, int error);
|
|
|
|
/* Interrupt management */
|
|
static void kmc_updints(int32 k);
|
|
static int32 kmc_AintAck (void);
|
|
static int32 kmc_BintAck (void);
|
|
|
|
/* DUP access */
|
|
|
|
/* Debug support */
|
|
static t_bool kmc_printBufferIn (int32 k, DEVICE *dev, uint8 line, t_bool rx,
|
|
int32 count, int32 ba, uint16 sel6v);
|
|
static t_bool kmc_printBDL(int32 k, uint32 dbits, DEVICE *dev, uint8 line, int32 ba, int prbuf);
|
|
|
|
/* Environment */
|
|
static const char *kmc_verifyUcode (int32 k);
|
|
|
|
/* Queue management */
|
|
static void initqueue (QH *head, int32 *count, int32 max, void *list, size_t size);
|
|
/* Convenience for initqueue() calls */
|
|
# define MAX_LIST_SIZE(q) DIM(q), (q), sizeof(q[0])
|
|
# define INIT_HDR_ONLY 0, NULL, 0
|
|
|
|
static t_bool insqueue (QH *entry, QH *pred, int32 *count, int32 max);
|
|
static void *remqueue (QH *entry, int32 *count);
|
|
|
|
|
|
/*
|
|
* Reset KMC device. This resets ALL the KMCs:
|
|
*/
|
|
|
|
static t_stat kmc_reset(DEVICE* dptr) {
|
|
int32 k;
|
|
size_t i;
|
|
|
|
if (sim_switches & SWMASK ('P')) {
|
|
for (i = 0; i < DIM (dupState); i++) {
|
|
dupstate *d = &dupState[i];
|
|
d->kmc = -1;
|
|
d->dupidx = -1;
|
|
d->linespeed = DFLT_SPEED;
|
|
}
|
|
}
|
|
|
|
for (k = 0; ((uint32)k) < kmc_dev.numunits; k++) {
|
|
sim_debug (DF_INF, dptr, "KMC%d: Reset\n", k);
|
|
|
|
/* One-time initialization of UNITs, one/direction/line */
|
|
for (i = 0; i < MAX_ACTIVE; i++) {
|
|
if (!tx_units[i][k].action) {
|
|
memset (&tx_units[i][k], 0, sizeof tx_units[0][0]);
|
|
memset (&rx_units[i][k], 0, sizeof tx_units[0][0]);
|
|
|
|
tx_units[i][k].action = &kmc_txService;
|
|
tx_units[i][k].flags = UNIT_IDLE;
|
|
tx_units[i][k].capac = 0;
|
|
tx_units[i][k].unit_kmc = k;
|
|
tx_units[i][k].unit_line = i;
|
|
|
|
rx_units[i][k].action = &kmc_rxService;
|
|
rx_units[i][k].flags = UNIT_IDLE;
|
|
rx_units[i][k].capac = 0;
|
|
rx_units[i][k].unit_kmc = k;
|
|
rx_units[i][k].unit_line = i;
|
|
}
|
|
}
|
|
kmc_masterClear (k); /* If previously running, halt */
|
|
|
|
if (sim_switches & SWMASK ('P'))
|
|
gflags &= ~FLG_INIT;
|
|
|
|
if (!(gflags & FLG_INIT)) { /* Power-up reset */
|
|
sel0 = 0x00aa;
|
|
sel2 = 0xa5a5;
|
|
sel4 = 0xdead;
|
|
sel6 = 0x5a5a;
|
|
memset (ucode, 0xcc, sizeof ucode);
|
|
memset (dram, 0xdd, sizeof dram);
|
|
gflags |= FLG_INIT;
|
|
gflags &= ~FLG_UCINI;
|
|
sim_register_internal_device (&kmc_int_rxdev);
|
|
}
|
|
}
|
|
|
|
kmc_int_rxdev.flags &= ~DEV_DIS; /* Make internal RX device */
|
|
kmc_int_rxdev.flags |= (kmc_dev.flags & DEV_DIS); /* enable/disable track KDP device */
|
|
|
|
return auto_config (dptr->name, ((dptr->flags & DEV_DIS)? 0: dptr->numunits)); /* auto config */
|
|
}
|
|
|
|
|
|
/*
|
|
* Read registers:
|
|
*/
|
|
|
|
static t_stat kmc_readCsr (int32* data, int32 PA, int32 access) {
|
|
int32 k;
|
|
|
|
k = ((PA-((DIB *)kmc_dev.ctxt)->ba) / IOLN_KMC);
|
|
|
|
switch ((PA >> 1) & 03) {
|
|
case 00:
|
|
*data = sel0;
|
|
break;
|
|
case 01:
|
|
*data = sel2;
|
|
break;
|
|
case 02:
|
|
if ((sel0 & SEL0_RMO) && (sel0 & SEL0_RMI)) {
|
|
*data = mni;
|
|
} else {
|
|
*data = sel4;
|
|
}
|
|
break;
|
|
case 03:
|
|
if (sel0 & SEL0_RMO) {
|
|
if (sel0 & SEL0_RMI) {
|
|
*data = mni;
|
|
} else {
|
|
*data = ucode[mna];
|
|
}
|
|
} else {
|
|
*data = sel6;
|
|
}
|
|
break;
|
|
}
|
|
|
|
sim_debug (DF_RGR, &kmc_dev, "KMC%u CSR rd: addr=0%06o SEL%d, data=%06o 0x%04x access=%d\n",
|
|
k, PA, PA & 07, *data, *data, access);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/*
|
|
* Write registers:
|
|
*/
|
|
|
|
static t_stat kmc_writeCsr (int32 data, int32 PA, int32 access) {
|
|
uint32 changed;
|
|
int reg = PA & 07;
|
|
int sel = (PA >> 1) & 03;
|
|
int32 k;
|
|
|
|
k = ((PA-((DIB *)kmc_dev.ctxt)->ba) / IOLN_KMC);
|
|
|
|
if (access == WRITE) {
|
|
sim_debug (DF_RGW, &kmc_dev, "KMC%u CSR wr: addr=0%06o SEL%d, data=%06o 0x%04x\n",
|
|
k, PA, reg, data, data);
|
|
} else {
|
|
sim_debug (DF_RGW, &kmc_dev, "KMC%u CSR wr: addr=0%06o BSEL%d, data=%06o 0x%04x\n",
|
|
k, PA, reg, data, data);
|
|
}
|
|
|
|
switch (sel) {
|
|
case 00: /* SEL0 */
|
|
if (access == WRITEB) {
|
|
data = (PA & 1)
|
|
? (((data & 0377) << 8) | (sel0 & 0377))
|
|
: ((data & 0377) | (sel0 & 0177400));
|
|
}
|
|
changed = sel0 ^ data;
|
|
sel0 = (uint16)data;
|
|
if (sel0 & SEL0_MRC) {
|
|
if (((sel0 & SEL0_RUN) == 0) && (changed & SEL0_RUN)) {
|
|
kmc_halt (k, HALT_MRC);
|
|
}
|
|
kmc_masterClear(k);
|
|
break;
|
|
}
|
|
if (!(data & SEL0_RUN)) {
|
|
if (data & SEL0_RMO) {
|
|
if ((changed & SEL0_CWR) && (data & SEL0_CWR)) { /* CWR rising */
|
|
ucode[mna] = sel6;
|
|
sel4 = ucode[mna]; /* Copy contents to sel 4 */
|
|
}
|
|
} else {
|
|
if (changed & SEL0_RMO) { /* RMO falling */
|
|
sel4 = mna;
|
|
}
|
|
}
|
|
if ((data & SEL0_RMI) && (changed & SEL0_RMI)) {
|
|
mni = sel6;
|
|
}
|
|
if ((data & SEL0_SUP) && (changed & SEL0_SUP)) {
|
|
if (data & SEL0_RMI) {
|
|
kmc_doMicroinstruction(k, mni);
|
|
} else {
|
|
kmc_doMicroinstruction(k, ucode[upc++]);
|
|
}
|
|
}
|
|
}
|
|
if (changed & SEL0_RUN) { /* Changing the run bit? */
|
|
if (sel0 & SEL0_RUN) {
|
|
kmc_startUcode (k);
|
|
} else {
|
|
kmc_halt (k, HALT_STOP);
|
|
}
|
|
}
|
|
if (changed & (SEL0_IEI | SEL0_IEO))
|
|
kmc_updints (k);
|
|
|
|
if ((sel0 & SEL0_RUN)) {
|
|
if ((sel0 & SEL0_RQI) && !(sel2 & SEL2_RDO))
|
|
sel2 = (sel2 & 0xFF00) | SEL2_RDI; /* Clear command bits too */
|
|
kmc_updints(k);
|
|
}
|
|
break;
|
|
case 01: /* SEL2 */
|
|
if (access == WRITEB) {
|
|
data = (PA & 1)
|
|
? (((data & 0377) << 8) | (sel2 & 0377))
|
|
: ((data & 0377) | (sel2 & 0177400));
|
|
}
|
|
if (sel0 & SEL0_RUN) {
|
|
/* Handle commands in and out.
|
|
* Output takes priority, but after servicing an
|
|
* output, an input request must be serviced even
|
|
* if another output command is ready.
|
|
*/
|
|
if ((sel2 & SEL2_RDO) && (!(data & SEL2_RDO))) {
|
|
sel2 = (uint16)data; /* RDO clearing, RDI can't be set */
|
|
if (sel0 & SEL0_RQI) {
|
|
sel2 = (sel2 & 0xFF00) | SEL2_RDI;
|
|
kmc_updints(k);
|
|
} else
|
|
kmc_processCompletions(k);
|
|
} else {
|
|
if ((sel2 & SEL2_RDI) && (!(data & SEL2_RDI))) {
|
|
sel2 = (uint16)data; /* RDI clearing, RDO can't be set */
|
|
kmc_dispatchInputCmd(k); /* Can set RDO */
|
|
if ((sel0 & SEL0_RQI) && !(sel2 & SEL2_RDO))
|
|
sel2 = (sel2 & 0xFF00) | SEL2_RDI;
|
|
kmc_updints(k);
|
|
} else {
|
|
sel2 = (uint16)data;
|
|
}
|
|
}
|
|
} else {
|
|
sel2 = (uint16)data;
|
|
}
|
|
break;
|
|
case 02: /* SEL4 */
|
|
mna = data & (KMC_CRAMSIZE -1);
|
|
sel4 = (uint16)data;
|
|
break;
|
|
case 03: /* SEL6 */
|
|
if (sel0 & SEL0_RMI) {
|
|
mni = (uint16)data;
|
|
}
|
|
sel6 = (uint16)data;
|
|
break;
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Microengine simulator
|
|
*
|
|
* This simulates a small subset of the KMC11-A's instruction set.
|
|
* This is necessary because the operating systems force microprocessor
|
|
* to execute these instructions in order to load (and extract) state.
|
|
* These are implemented here so that the OS tools are happy, and to
|
|
* give their error logging tools something to do.
|
|
*/
|
|
|
|
static void kmc_doMicroinstruction (int32 k, uint16 instr) {
|
|
switch (instr) {
|
|
case 0041222: /* MOVE <MEM><BSEL2> */
|
|
sel2 = (sel2 & ~0xFF) | (dram[mar%KMC_DRAMSIZE] & 0xFF);
|
|
break;
|
|
case 0055222: /* MOVE <MRM><BSEL2><MARINC> */
|
|
sel2 = (sel2 & ~0xFF) | (dram[mar%KMC_DRAMSIZE] & 0xFF);
|
|
mar = (mar +1)%KMC_DRAMSIZE;
|
|
break;
|
|
case 0122440: /* MOVE <BSEL2><MEM> */
|
|
dram[mar%KMC_DRAMSIZE] = sel2 & 0xFF;
|
|
break;
|
|
case 0136440: /* MOVE <BSEL2><MEM><MARINC> */
|
|
dram[mar%KMC_DRAMSIZE] = sel2 & 0xFF;
|
|
mar = (mar +1)%KMC_DRAMSIZE;
|
|
break;
|
|
case 0121202: /* MOVE <NPR><BSEL2> */
|
|
case 0021002: /* MOVE <IBUS 0><BSEL2> */
|
|
sel2 = (sel2 & ~0xFF) | 0;
|
|
break;
|
|
default:
|
|
if ((instr & 0160000) == 0000000) { /* MVI */
|
|
switch (instr & 0174000) {
|
|
case 0010000: /* Load MAR LOW */
|
|
mar = (mar & 0xFF00) | (instr & 0xFF);
|
|
break;
|
|
case 0004000: /* Load MAR HIGH */
|
|
mar = (mar & 0x00FF) | ((instr & 0xFF)<<8);
|
|
break;
|
|
default: /* MVI NOP / MVI INC */
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
if ((instr & 0163400) == 0100400) {
|
|
upc = ((instr & 0014000) >> 3) | (instr & 0377);
|
|
sim_debug (DF_INF, &kmc_dev, "KMC%u microcode start uPC %04o\n", k, upc);
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Transmitter service.
|
|
*
|
|
* Each line has a TX unit. This thread handles message transmission.
|
|
*
|
|
* A host-supplied buffer descriptor list is walked and the data handed off
|
|
* to the line. Because the DUP emulator can't abort a transmission in progress,
|
|
* and wants to receive formatted messages, the data from host memory is
|
|
* accumulated into an intermediate buffer.
|
|
*
|
|
* TX BUFFER OUT completions are delivered as the data is retrieved, which happens
|
|
* at approximately the 'line speed'; this does not indicate that the data is on
|
|
* the wire. Only a DDCMP ACK confirms receipt.
|
|
*
|
|
* TX CONTROL OUT completions are generated for errors.
|
|
*
|
|
* The buffer descriptor flags for resync, start and end of message are obeyed.
|
|
*
|
|
* The transmitter asserts RTS at the beginning of a message, and clears it when
|
|
* idle.
|
|
*
|
|
* No more than one message is on the wire at any time.
|
|
*
|
|
* The DUP may be speed limited, delivering transmit completions at line speed.
|
|
* In that case, the KDP and DUP limits will interact.
|
|
*
|
|
* This thread wakes:
|
|
* o When a new buffer descriptor is delivered by the host
|
|
* o When a state machine timeout expires
|
|
* o When a DUP transmit completion occurs.
|
|
*
|
|
*/
|
|
|
|
static t_stat kmc_txService (UNIT *txup) {
|
|
int32 k = txup->unit_kmc;
|
|
dupstate *d = line2dup[txup->unit_line];
|
|
t_bool more;
|
|
|
|
ASSURE ((k >= 0) && (k < (int32) kmc_dev.numunits) && (d->kmc == k) &&
|
|
(d->line == txup->unit_line));
|
|
|
|
/* Provide the illusion of progress. */
|
|
upc = 1 + ((upc + 1) % (KMC_CRAMSIZE -1));
|
|
|
|
/* Process one buffer descriptor per cycle
|
|
* CAUTION: this switch statement uses fall-through cases.
|
|
*/
|
|
|
|
/* State control macros.
|
|
*
|
|
* All exit the current statement (think "break" or "continue")
|
|
*
|
|
* Change to newstate and process immediately
|
|
*/
|
|
|
|
#define TXSTATE(newstate) { \
|
|
d->txstate = newstate; \
|
|
more = FALSE; \
|
|
continue; }
|
|
|
|
/* Change to newstate after a delay of time. */
|
|
#define TXDELAY(newstate,time) { \
|
|
txup->wait = time; \
|
|
d->txstate = newstate; \
|
|
more = FALSE; \
|
|
break; }
|
|
|
|
/* Stop processing an return to IDLE.
|
|
* This will NOT check for more work - it is used
|
|
* primarily in error conditions.
|
|
*/
|
|
#define TXSTOP { \
|
|
d->txstate = TXIDLE; \
|
|
more = FALSE; \
|
|
break; }
|
|
|
|
do {
|
|
more = TRUE;
|
|
|
|
if (d->txstate > TXRTS) {
|
|
sim_debug (DF_BUF, &kmc_dev, "KMC%u line %u: transmit service %s state = %u\n",
|
|
k, txup->unit_line, (more? "continued": "activated"), d->txstate);
|
|
}
|
|
|
|
switch (d->txstate) {
|
|
case TXDONE: /* Resume from completions */
|
|
d->txstate = TXIDLE;
|
|
|
|
case TXIDLE: /* Check for new BD */
|
|
if (!kmc_txNewBdl(d)) {
|
|
TXSTOP;
|
|
}
|
|
d->txmlen =
|
|
d->txslen = 0;
|
|
d->txstate = TXRTS;
|
|
|
|
if (dup_set_RTS (d->dupidx, TRUE) != SCPE_OK) {
|
|
sim_debug (DF_CTO, &kmc_dev, "KMC%u line %u: dup: %d DUP CSR NXM\n",
|
|
k, d->line, d->dupidx);
|
|
kmc_ctrlOut (k, SEL6_CO_NXM, 0, d->line, 0);
|
|
}
|
|
|
|
|
|
case TXRTS: /* Wait for CTS */
|
|
if (dup_get_CTS (d->dupidx) <= 0) {
|
|
TXDELAY (TXRTS, TXCTS_DELAY);
|
|
}
|
|
|
|
d->txstate = TXSOM;
|
|
|
|
sim_debug (DF_BUF, &kmc_dev, "KMC%u line %u: transmitting bdl=%06o\n",
|
|
k, txup->unit_line, d->tx.bda);
|
|
case TXSOM: /* Start assembling a message */
|
|
if (!(d->tx.bd[2] & BDL_SOM)) {
|
|
sim_debug (DF_ERR, &kmc_dev, "KMC%u line %u: TX BDL not SOM\n", k, d->line);
|
|
kmc_halt (k, HALT_XSOM);
|
|
TXSTOP;
|
|
}
|
|
|
|
if (d->tx.bd[2] & BDL_RSY) {
|
|
static const uint8 resync[8] = { DDCMP_SYN, DDCMP_SYN, DDCMP_SYN, DDCMP_SYN,
|
|
DDCMP_SYN, DDCMP_SYN, DDCMP_SYN, DDCMP_SYN, };
|
|
if (!d->txmsg || (d->txmsize < sizeof (resync))) {
|
|
d->txmsize = 8 + sizeof (resync);
|
|
d->txmsg = (uint8 *)realloc (d->txmsg, 8 + sizeof (resync));
|
|
}
|
|
memcpy (d->txmsg, resync, sizeof (resync));
|
|
d->txmlen =
|
|
d->txslen = sizeof (resync);
|
|
}
|
|
|
|
d->txstate = TXHDR;
|
|
|
|
case TXHDR: /* Assemble the header */
|
|
if (!kmc_txAppendBuffer(d)) { /* NXM - Try next list */
|
|
TXDELAY (TXDONE, TXDONE_DELAY);
|
|
}
|
|
TXDELAY (TXHDRX, XTIME (d->tx.bd[1], d->linespeed));
|
|
|
|
case TXHDRX: /* Report header descriptor done */
|
|
if (!kmc_bufferAddressOut (k, 0, 0, d->line, d->tx.bda)) {
|
|
TXDELAY (TXDONE, TXDONE_DELAY);
|
|
}
|
|
if (!(d->tx.bd[2] & BDL_EOM)) {
|
|
if (kmc_txNewBd(d)) {
|
|
TXSTATE (TXHDR);
|
|
}
|
|
/* Not EOM, no more BDs - underrun or NXM. In theory
|
|
* this should have waited on char time before declaring
|
|
* underrun, but no sensible OS would live that dangerously.
|
|
*/
|
|
TXDELAY (TXDONE, TXDONE_DELAY);
|
|
}
|
|
|
|
/* EOM. Control messages are always complete */
|
|
if (d->txmsg[d->txslen+0] == DDCMP_ENQ) {
|
|
TXSTATE (TXRDY);
|
|
}
|
|
|
|
/* EOM expecting data to follow.
|
|
* However, if the OS computes and includes HRC in a data/MOP message, this can
|
|
* be the last descriptor. In that case, this is EOM.
|
|
*/
|
|
if (d->tx.bd[2] & BDL_LDS) {
|
|
TXSTATE (TXMRDY);
|
|
break;
|
|
}
|
|
|
|
/* Data sent in a separate descriptor */
|
|
|
|
if (!kmc_txNewBd(d)) {
|
|
TXDELAY (TXDONE, TXDONE_DELAY);
|
|
}
|
|
|
|
if (!(d->tx.bd[2] & BDL_SOM)) {
|
|
kmc_halt (k, HALT_XSOM2);
|
|
sim_debug (DF_ERR, &kmc_dev, "KMC%u line %u: TX BDL not SOM\n", k, d->line);
|
|
TXSTOP;
|
|
}
|
|
d->txstate = TXDATA;
|
|
|
|
case TXDATA: /* Assemble data/maint payload */
|
|
if (!kmc_txAppendBuffer(d)) { /* NXM */
|
|
TXDELAY (TXDONE, TXDONE_DELAY);
|
|
}
|
|
TXDELAY (TXDATAX, XTIME (d->tx.bd[1], d->linespeed));
|
|
|
|
case TXDATAX: /* Report BD completion */
|
|
if (!kmc_bufferAddressOut (k, 0, 0, d->line, d->tx.bda)) {
|
|
TXDELAY (TXDONE, TXDONE_DELAY);
|
|
}
|
|
if (d->tx.bd[2] & BDL_EOM) {
|
|
TXSTATE (TXRDY);
|
|
}
|
|
if (!kmc_txNewBd(d)) {
|
|
TXDELAY (TXDONE, TXDONE_DELAY);
|
|
}
|
|
TXSTATE (TXDATA);
|
|
|
|
/* These states hand-off the message to the DUP.
|
|
* txService suspends until transmit complete.
|
|
* Note that txComplete can happen within the calls to the DUP.
|
|
*/
|
|
case TXMRDY: /* Data with OS-embedded HCRC */
|
|
d->txstate = TXACT;
|
|
ASSURE (d->txmsg[d->txslen + 0] != DDCMP_ENQ);
|
|
ASSURE (((d->txmlen - d->txslen) > 8) && /* Data, length should match count */
|
|
(((size_t)(((d->txmsg[d->txslen + 2] & 077) << 8) | d->txmsg[d->txslen + 1])) ==
|
|
(d->txmlen - (d->txslen + 8))));
|
|
if (!dup_put_msg_bytes (d->dupidx, d->txmsg + d->txslen, d->txmlen - d->txslen, TRUE, TRUE)) {
|
|
sim_debug (DF_PKT, &kmc_dev, "KMC%u line %u: DUP%d refused TX packet\n", k, d->line, d->dupidx);
|
|
TXDELAY (TXMRDY, TXDUP_DELAY);
|
|
}
|
|
more = FALSE;
|
|
break;
|
|
|
|
case TXRDY: /* Control or DATA with KDP-CRCH */
|
|
d->txstate = TXACT; /* Note that DUP can complete before returning */
|
|
if (d->txmsg[d->txslen + 0] == DDCMP_ENQ) { /* Control message */
|
|
ASSURE ((d->txmlen - d->txslen) == 6);
|
|
if (!dup_put_msg_bytes (d->dupidx, d->txmsg, d->txslen + 6, TRUE, TRUE)) {
|
|
sim_debug (DF_PKT, &kmc_dev, "KMC%u line %u: DUP%d refused TX packet\n", k, d->line, d->dupidx);
|
|
TXDELAY (TXRDY, TXDUP_DELAY);
|
|
}
|
|
more = FALSE;
|
|
break;
|
|
}
|
|
|
|
ASSURE (((d->txmlen - d->txslen) > 6) && /* Data, length should match count */
|
|
(((size_t)(((d->txmsg[d->txslen + 2] & 077) << 8) | d->txmsg[d->txslen + 1])) ==
|
|
(d->txmlen - (d->txslen + 6))));
|
|
if (!dup_put_msg_bytes (d->dupidx, d->txmsg, d->txslen + 6, TRUE, TRUE)) {
|
|
sim_debug (DF_PKT, &kmc_dev, "KMC%u line %u: DUP%d refused TX packet\n", k, d->line, d->dupidx);
|
|
TXDELAY (TXRDY, TXDUP_DELAY);
|
|
}
|
|
if (!dup_put_msg_bytes (d->dupidx, d->txmsg + d->txslen + 6, d->txmlen - (d->txslen + 6), FALSE, TRUE)) {
|
|
sim_debug (DF_PKT, &kmc_dev, "KMC%u line %u: DUP%d refused TX packet\n", k, d->line, d->dupidx);
|
|
TXDELAY (TXRDY, TXDUP_DELAY);
|
|
}
|
|
more = FALSE;
|
|
break;
|
|
|
|
/* Active should never be reached as txService is not
|
|
* scheduled while transmission is in progress.
|
|
* txComplete will reset txstate based on the final descriptor.
|
|
*/
|
|
default:
|
|
case TXACT:
|
|
sim_debug (DF_PKT, &kmc_dev, "KMC%u line %u: kmc_txService called while active\n", k, d->line);
|
|
TXSTOP;
|
|
}
|
|
} while (more);
|
|
#undef TXSTATE
|
|
#undef TXDELAY
|
|
#undef TXSTOP
|
|
|
|
if (d->txstate == TXIDLE) {
|
|
ASSURE (!d->txavail);
|
|
if (dup_set_RTS (d->dupidx, FALSE) != SCPE_OK) {
|
|
sim_debug (DF_CTO, &kmc_dev, "KMC%u line %u: dup: %d DUP CSR NXM\n",
|
|
k, d->line, d->dupidx);
|
|
kmc_ctrlOut (k, SEL6_CO_NXM, 0, d->line, 0);
|
|
}
|
|
} else {
|
|
if (d->txstate != TXACT)
|
|
sim_activate_after(txup, txup->wait);
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Receiver service
|
|
*
|
|
* Each line has an RX unit. This service thread accepts incoming
|
|
* messages from the DUP, and delivers the data into buffer descriptors
|
|
* provided by the host.
|
|
*
|
|
* Polling is not necessary if the DUP provides input notification; otherwise
|
|
* it's at a rate of a few character times at 19,200 - the maximum line speed.
|
|
*
|
|
* Once a message has been accepted, the thread looks for a host-provided
|
|
* buffer descriptor. If one doesn't appear in a reasonable time (in hardware,
|
|
* it's one character time - this code is more generous), the message is dropped
|
|
* and a RX CONTROL OUT is issued.
|
|
*
|
|
* Then the message is parsed, validated and delivered to the host buffer.
|
|
*
|
|
* Any CRC errors generate RX CONTROL OUTs. RX BUFFER OUTs are generated for each
|
|
* descriptor filled, and at of message for the last descriptor used.
|
|
*
|
|
* If the line is configured for Secondary Station address matching, the
|
|
* message is discarded if the address does not match (and the CRC validated).
|
|
*
|
|
* After generating a RX BUFFER OUT, the thread suspends prior to filling the
|
|
* next descriptor.
|
|
*
|
|
* If reception is killed, the RX state machine is reset by the RX BUFFER IN.
|
|
*
|
|
* This thread wakes:
|
|
* o When a RX BUFFER IN delivers a buffer
|
|
* o When timeouts between states expire.
|
|
* o When the DUP notifies it of a new message received.
|
|
*/
|
|
|
|
static t_stat kmc_rxService (UNIT *rxup) {
|
|
int32 k = rxup->unit_kmc;
|
|
dupstate *d = line2dup[rxup->unit_line];
|
|
BDL *bdl;
|
|
t_stat r;
|
|
uint16 xrem, seglen;
|
|
|
|
ASSURE ((k >= 0) && (k < (int32) kmc_dev.numunits) && (d->kmc == k) &&
|
|
(d->line == rxup->unit_line));
|
|
|
|
if (d->rxstate > RXBDL) {
|
|
sim_debug (DF_BUF, &kmc_dev, "KMC%u line %u: receive service activated state = %u\n",
|
|
k, rxup->unit_line, d->rxstate);
|
|
}
|
|
|
|
/* Provide the illusion of progress. */
|
|
upc = 1 + ((upc + 1) % (KMC_CRAMSIZE -1));
|
|
|
|
rxup->wait = RXPOLL_DELAY;
|
|
|
|
/* CAUTION: This switch statement uses fall-through cases
|
|
*
|
|
* The KILL logic in kmc_rxBufferIn tracks these states.
|
|
*/
|
|
|
|
switch (d->rxstate) {
|
|
case RXIDLE:
|
|
rxup->wait = RXPOLL_DELAY;
|
|
|
|
r = dup_get_packet (d->dupidx, (const uint8 **)&d->rxmsg, &d->rxmlen);
|
|
if (r == SCPE_LOST) {
|
|
kmc_updateDSR (d);
|
|
break;
|
|
}
|
|
if ((r != SCPE_OK) || (d->rxmsg == NULL)) { /* No packet? */
|
|
rxup->wait = tmxr_poll;
|
|
break;
|
|
}
|
|
|
|
if (!(d->ctrlFlags & SEL6_CI_ENABLE)) {
|
|
break;
|
|
}
|
|
|
|
while (d->rxmlen && (d->rxmsg[0] == DDCMP_SYN)) {
|
|
d->rxmsg++;
|
|
d->rxmlen--;
|
|
}
|
|
if (d->rxmlen < 8) {
|
|
break;
|
|
}
|
|
|
|
if (!((d->rxmsg[0] == DDCMP_SOH) ||
|
|
(d->rxmsg[0] == DDCMP_ENQ) ||
|
|
(d->rxmsg[0] == DDCMP_DLE))) {
|
|
|
|
/* Toggling RCVEN causes the DUP receiver to resync.
|
|
*/
|
|
#ifdef DUP_RXRESYNC
|
|
dup_set_RCVEN (d->dupidx, FALSE);
|
|
dup_set_RCVEN (d->dupidx, TRUE);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
d->rxstate = RXBDL;
|
|
d->rxused = 0;
|
|
|
|
if (DEBUG_PRS (kmc_dev)) {
|
|
if (d->rxmsg[0] == DDCMP_ENQ) {
|
|
static const char *const ctlnames [] = {
|
|
"00", "ACK", "NAK", "REP", "04", "05", "STRT", "STACK" };
|
|
uint8 type = d->rxmsg[1];
|
|
|
|
sim_debug (DF_BUF, &kmc_dev, "KMC%u line %u: receiving %s\n",
|
|
k, rxup->unit_line,
|
|
((type >= DIM (ctlnames))? "UNKNOWN" : ctlnames[type]));
|
|
} else {
|
|
sim_debug (DF_BUF, &kmc_dev, "KMC%u line %u: receiving %s len=%u\n",
|
|
k, rxup->unit_line,
|
|
((d->rxmsg[0] == DDCMP_SOH)? "DATA" : "MAINT"), d->rxmlen);
|
|
}
|
|
}
|
|
|
|
case RXBDL:
|
|
if (!(bdl = (BDL *)remqueue(d->rxqh.next, &d->rxavail))) {
|
|
rxup->wait = RXBDL_DELAY;
|
|
d->rxstate = RXNOBUF;
|
|
break;
|
|
}
|
|
d->rx.bda = bdl->ba;
|
|
ASSURE (insqueue (&bdl->hdr, d->bdqh.prev, &d->bdavail, DIM(d->bdq)));
|
|
|
|
sim_debug (DF_BUF, &kmc_dev, "KMC%u line %u: receiving bdl=%06o\n",
|
|
k, rxup->unit_line, d->rx.bda);
|
|
|
|
if (Map_ReadW (d->rx.bda, 3*2, d->rx.bd)) {
|
|
kmc_ctrlOut (k, SEL6_CO_NXM, SEL2_IOT, d->line, d->rx.bda);
|
|
d->rxstate = RXIDLE;
|
|
break;
|
|
}
|
|
d->rxstate = RXBUF;
|
|
|
|
case RXBUF:
|
|
d->rx.ba = ((d->rx.bd[2] & BDL_XAD) << BDL_S_XAD) | d->rx.bd[0];
|
|
if (d->rx.bd[1] == 0) {
|
|
sim_debug (DF_ERR, &kmc_dev, "KMC%u line %u: RX buffer descriptor size is zero\n", k, d->line);
|
|
kmc_halt (k, HALT_MTRCV);
|
|
d->rxstate = RXIDLE;
|
|
break;
|
|
}
|
|
d->rx.rcvc = 0;
|
|
d->rxdlen = 0;
|
|
d->rxstate = RXDAT;
|
|
|
|
case RXDAT:
|
|
more:
|
|
if (d->rxused < 8) {
|
|
seglen = 6 - d->rxused;
|
|
} else {
|
|
seglen = d->rxmlen - (d->rxused +2);
|
|
}
|
|
if (seglen > d->rx.bd[1]) {
|
|
seglen = d->rx.bd[1];
|
|
}
|
|
ASSURE (seglen > 0);
|
|
|
|
xrem = (uint16)Map_WriteB (d->rx.ba, seglen, d->rxmsg + d->rxused);
|
|
if (xrem != 0) {
|
|
uint16 bd[3];
|
|
memcpy (bd, &d->rx.bd, sizeof bd);
|
|
seglen -= xrem;
|
|
d->rx.rcvc += seglen;
|
|
bd[1] = d->rx.rcvc;
|
|
kmc_updateBDCount (d->rx.bda, bd); /* Unchecked because already reporting NXM */
|
|
kmc_ctrlOut (k, SEL6_CO_NXM, SEL2_IOT, d->line, d->rx.bda);
|
|
d->rxstate = RXIDLE;
|
|
break;
|
|
}
|
|
d->rx.ba += seglen;
|
|
d->rx.rcvc += seglen;
|
|
d->rxused += seglen;
|
|
|
|
if (d->rxused == 6) {
|
|
if (0 != ddcmp_crc16 (0, d->rxmsg, 8)) {
|
|
sim_debug (DF_PKT, &kmc_dev, "KMC%u line %u: HCRC Error for %d byte packet\n",
|
|
k, d->line, d->rxmlen);
|
|
#ifdef DUP_RXRESYNC
|
|
dup_set_RCVEN (d->dupidx, FALSE);
|
|
dup_set_RCVEN (d->dupidx, TRUE);
|
|
#endif
|
|
kmc_ctrlOut (k, SEL6_CO_HCRC, SEL2_IOT, d->line, d->rx.bda);
|
|
d->rxstate = RXIDLE;
|
|
break;
|
|
}
|
|
d->rxused += 2;
|
|
d->linkstate &= ~LINK_SEL;
|
|
if (d->rxmsg[2] & 0x80) {
|
|
d->linkstate |= LINK_SEL;
|
|
}
|
|
if (d->ctrlFlags & SEL6_CI_ENASS) { /* Note that spec requires first bd >= 6 if SS match enabled */
|
|
if (!(d->rxmsg[5] == (d->ctrlFlags & SEL6_CI_SADDR))) { /* Also include SELECT? */
|
|
ASSURE ((bdl = (BDL *)remqueue(d->bdqh.prev, &d->bdavail)) != NULL);
|
|
ASSURE (bdl->ba == d->rx.bda);
|
|
ASSURE (insqueue (&bdl->hdr, &d->rxqh, &d->rxavail, MAXQUEUE));
|
|
d->rxstate = RXIDLE;
|
|
break;
|
|
}
|
|
}
|
|
d->rxdlen = ((d->rxmsg[2] &~ 0300) << 8) | d->rxmsg[1];
|
|
}
|
|
if (((d->rxused == 8) && (d->rxmsg[0] == DDCMP_ENQ)) ||
|
|
(((d->rxused - 8) == d->rxdlen) && (d->rxmsg[0] != DDCMP_ENQ))) { /* End of message */
|
|
|
|
/* Issue completion after the nominal reception delay */
|
|
|
|
rxup->wait = XTIME (d->rx.rcvc+2, d->linespeed);
|
|
d->rxstate = RXLAST;
|
|
break;
|
|
}
|
|
if (d->rx.rcvc < d->rx.bd[1]) {
|
|
goto more;
|
|
}
|
|
|
|
/* This descriptor is full. No need to update its bc.
|
|
* Issue the completion after the nominal reception delay.
|
|
*/
|
|
d->rxstate = RXFULL;
|
|
rxup->wait = XTIME (d->rx.bd[1], d->linespeed);
|
|
break;
|
|
|
|
case RXLAST:
|
|
/* End of message. Update final BD count, check data CRC & report either
|
|
* BUFFER OUT (with EOM) or error CONTROL OUT (NXM or DCRC).
|
|
*/
|
|
d->rx.bd[1] = d->rx.rcvc;
|
|
if (kmc_updateBDCount (d->rx.bda, d->rx.bd)) {
|
|
kmc_ctrlOut (k, SEL6_CO_NXM, SEL2_IOT, d->line, d->rx.bda);
|
|
} else {
|
|
if ((d->rxmsg[0] != DDCMP_ENQ) &&
|
|
(0 != ddcmp_crc16 (0, d->rxmsg +8, d->rxdlen+2))) {
|
|
sim_debug (DF_PKT, &kmc_dev, "KMC%u line %u: DCRC Error for %d byte packet\n",
|
|
k, d->line, d->rxmlen);
|
|
#ifdef DUP_RXRESYNC
|
|
dup_set_RCVEN (d->dupidx, FALSE);
|
|
dup_set_RCVEN (d->dupidx, TRUE);
|
|
#endif
|
|
kmc_ctrlOut (k, SEL6_CO_DCRC, SEL2_IOT, d->line, d->rx.bda);
|
|
} else {
|
|
kmc_bufferAddressOut (k, SEL6_BO_EOM, SEL2_IOT, d->line, d->rx.bda);
|
|
#ifdef DUP_RXRESYNC
|
|
if (d->rxmsg[2] & 0x40) { /* QSYNC? (Next message uses short sync) */
|
|
dup_set_RCVEN (d->dupidx, FALSE);
|
|
dup_set_RCVEN (d->dupidx, TRUE);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
rxup->wait = RXNEWBD_DELAY;
|
|
d->rxstate = RXIDLE;
|
|
break;
|
|
|
|
case RXFULL:
|
|
kmc_bufferAddressOut (k, 0, SEL2_IOT, d->line, d->rx.bda);
|
|
|
|
/* Advance to next descriptor */
|
|
|
|
if (d->rx.bd[2] & BDL_LDS) {
|
|
d->rxstate = RXBDL;
|
|
} else {
|
|
d->rx.bda += 3*2;
|
|
if (Map_ReadW (d->rx.bda, 3*2, d->rx.bd)) {
|
|
kmc_ctrlOut (k, SEL6_CO_NXM, SEL2_IOT, d->line, d->rx.bda);
|
|
d->rxstate = RXIDLE;
|
|
break;
|
|
}
|
|
sim_debug (DF_BUF, &kmc_dev, "KMC%u line %u: receiving bd=%06o\n",
|
|
k, rxup->unit_line, d->rx.bda);
|
|
d->rx.rcvc = 0; /* Set here in case of kill */
|
|
d->rxstate = RXBUF;
|
|
}
|
|
rxup->wait = RXNEWBD_DELAY;
|
|
break;
|
|
|
|
case RXNOBUF:
|
|
kmc_ctrlOut (k, SEL6_CO_NOBUF, SEL2_IOT, d->line, 0);
|
|
d->rxstate = RXIDLE;
|
|
break;
|
|
|
|
default:
|
|
ASSURE (FALSE);
|
|
}
|
|
|
|
if ((d->rxstate != RXIDLE) || d->rxavail) {
|
|
if (rxup->wait == tmxr_poll)
|
|
sim_clock_coschedule(rxup, tmxr_poll);
|
|
else
|
|
sim_activate_after(rxup, rxup->wait);
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/*
|
|
* master clear a KMC
|
|
*
|
|
* Master clear initializes the hardware, but does not clear any RAM.
|
|
* This includes the CSRs, which are a dual-ported RAM structure.
|
|
*
|
|
* There is no guarantee that any data structures are initialized.
|
|
*/
|
|
|
|
static void kmc_masterClear(int32 k) {
|
|
|
|
if (sim_deb) {
|
|
DEVICE *dptr = find_dev_from_unit (&tx_units[0][k]);
|
|
|
|
sim_debug (DF_INF, dptr, "KMC%d: Master clear\n", k);
|
|
}
|
|
|
|
if (sel0 & SEL0_RUN) {
|
|
kmc_halt (k, HALT_MRC);
|
|
}
|
|
|
|
/* Clear SEL1 (maint reg) - done by HW reset.
|
|
* Clear IE (HW doesn't, but it simplifies
|
|
* things & every user to date writes zeroes with MRC.
|
|
*/
|
|
sel0 &= SEL0_MRC | (0x00FF & ~(SEL0_IEO | SEL0_IEI));
|
|
upc = 0;
|
|
mar = 0;
|
|
mna = 0;
|
|
mni = 0;
|
|
|
|
kmc_updints (k);
|
|
}
|
|
|
|
/* Initialize the KMC state that is done by microcode */
|
|
|
|
static void kmc_startUcode (int32 k) {
|
|
int i;
|
|
const char *uname;
|
|
|
|
if ((uname = kmc_verifyUcode (k)) == NULL) {
|
|
sim_debug (DF_INF, &kmc_dev, "KMC%u: microcode not loaded, won't run\n", k);
|
|
kmc_halt (k, HALT_BADUC);
|
|
return;
|
|
}
|
|
|
|
sim_debug (DF_INF, &kmc_dev, "KMC%u started %s microcode at uPC %04o\n",
|
|
k, uname, upc);
|
|
|
|
if (upc != 0) { /* Resume from cleared RUN */
|
|
if (gflags & FLG_UCINI) {
|
|
for (i = 0; i < MAX_ACTIVE; i++) {
|
|
UNIT *up = &tx_units[i][k];
|
|
|
|
if (up->unit_htime) {
|
|
sim_activate (up, up->unit_htime);
|
|
}
|
|
up = &rx_units[i][k];
|
|
if (up->unit_htime) {
|
|
sim_activate (up, up->unit_htime);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
kmc_halt (k, HALT_BADRES);
|
|
return;
|
|
}
|
|
|
|
/* upc == 0: microcode initialization */
|
|
|
|
upc = 1;
|
|
|
|
/* CSRs */
|
|
|
|
sel0 &= 0xFF00;
|
|
sel2 = 0;
|
|
sel4 = 0;
|
|
sel6 = 0;
|
|
|
|
/* Line data */
|
|
|
|
/* Initialize OS mapping to least likely device. (To avoid validating everywhere.) */
|
|
|
|
for (i = 0; i < MAX_ACTIVE; i++) {
|
|
line2dup[i] = &dupState[DUP_LINES-1];
|
|
}
|
|
|
|
/* Initialize all the DUP structures, releasing any assigned to this KMC.
|
|
*
|
|
* Only touch the devices if they have previously been assigned to this KMC.
|
|
*/
|
|
|
|
for (i = 0; i < DUP_LINES; i++) {
|
|
dupstate *d = dupState + i;
|
|
|
|
if ((d->kmc == k) && (d->dupidx != -1)) {
|
|
/* Make sure no callbacks are issued.
|
|
* Hardware initialization is done by BASE IN.
|
|
*/
|
|
dup_set_callback_mode (i, NULL, NULL, NULL);
|
|
}
|
|
/* Initialize DUP state if dup is unassigned or previously assigned
|
|
* to this KMC. This releases the DUP, so restarting ucode will
|
|
* release devices, allowing another KMC to assign them if desired.
|
|
* This is a level of cooperation that the real devices don't have,
|
|
* but it helps catch configuration errors and keeps these data
|
|
* structures consistent. Don't deassign a device currently owned
|
|
* by another kmc!
|
|
*/
|
|
if ((d->kmc == k) || (d->kmc == -1)) {
|
|
d->dupidx = -1;
|
|
d->kmc = -1;
|
|
d->line = UNASSIGNED_LINE;
|
|
|
|
initqueue (&d->rxqh, &d->rxavail, INIT_HDR_ONLY);
|
|
initqueue (&d->txqh, &d->txavail, INIT_HDR_ONLY);
|
|
initqueue (&d->bdqh, &d->bdavail, MAX_LIST_SIZE(d->bdq));
|
|
|
|
d->rxstate = RXIDLE;
|
|
d->txstate = TXIDLE;
|
|
}
|
|
}
|
|
|
|
/* Completion queue */
|
|
|
|
initqueue(&cqueueHead, &cqueueCount, INIT_HDR_ONLY);
|
|
initqueue(&freecqHead, &freecqCount, MAX_LIST_SIZE(cqueue));
|
|
|
|
gflags |= FLG_UCINI;
|
|
kmc_updints (k);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Perform an input command
|
|
*
|
|
* The host must request ownership of the CSRs by setting RQI.
|
|
* If enabled, it gets an interrupt when RDI sets, allowing it
|
|
* to write the command. RDI and RDO are mutually exclusive.
|
|
*
|
|
* The microcode sets RDI by writing the entire BSEL2 with just RDI.
|
|
* This works because RDO can not be set at the same time as RDI.
|
|
*
|
|
* Mark P found that VMS drivers rely on this and BIS the command code.
|
|
* The COM IOP-DUP manual does say that all bits of BSEL2 are
|
|
* cleared on completion of a command. However, an output command could
|
|
* leave other bits on.
|
|
*
|
|
* Input commands are processed by the KMC when the host
|
|
* clears RDI. Upon completion of a command, 'all bits of bsel2
|
|
* are cleared by the KMC'. This is not implemented literally, since
|
|
* the processing of a command can result in an immediate completion,
|
|
* setting RDO and the other registers.
|
|
* Thus, although all bits are cleared before dispatching, RDO
|
|
* and the other other bits of BSEL2 may be set for a output command
|
|
* due to a completion if the host has cleared RQI.
|
|
*/
|
|
|
|
static void kmc_dispatchInputCmd(int32 k) {
|
|
uint8 line;
|
|
int32 ba;
|
|
int16 cmdsel2 = sel2;
|
|
dupstate* d;
|
|
|
|
line = (cmdsel2 & SEL2_LINE) >> SEL2_V_LINE;
|
|
|
|
sel2 &= ~0xFF; /* Clear BSEL2. */
|
|
if (sel0 & SEL0_RQI) /* If RQI was left on, grant the input request */
|
|
sel2 |= SEL2_RDI; /* here so any generated completions will block. */
|
|
|
|
if (line > MAX_LINE) {
|
|
sim_debug (DF_ERR, &kmc_dev, "KMC%u line %u: Line number is out of range\n", k, line);
|
|
kmc_halt (k, HALT_LINE);
|
|
return;
|
|
}
|
|
d = line2dup[line];
|
|
ba = ((sel6 & SEL6_CO_XAD) << (16-SEL6_V_CO_XAD)) | sel4;
|
|
|
|
sim_debug (DF_CMD, &kmc_dev, "KMC%u line %u: INPUT COMMAND sel2=%06o sel4=%06o sel6=%06o ba=%06o\n", k, line,
|
|
cmdsel2, sel4, sel6, ba);
|
|
|
|
switch (cmdsel2 & (SEL2_IOT | SEL2_CMD)) {
|
|
case CMD_BUFFIN: /* TX BUFFER IN */
|
|
kmc_txBufferIn(d, ba, sel6);
|
|
break;
|
|
case CMD_CTRLIN: /* CONTROL IN. */
|
|
case SEL2_IOT | CMD_CTRLIN:
|
|
kmc_ctrlIn (k, d, line);
|
|
break;
|
|
|
|
case CMD_BASEIN: /* BASE IN. */
|
|
kmc_baseIn (k, d, cmdsel2, line);
|
|
break;
|
|
|
|
case (SEL2_IOT | CMD_BUFFIN): /* Buffer in, receive buffer for us... */
|
|
kmc_rxBufferIn(d, ba ,sel6);
|
|
break;
|
|
default:
|
|
kmc_halt (k, HALT_BADCMD);
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
/* Process BASE IN command
|
|
*
|
|
* BASE IN assigns a line number to a DUP device, and marks it
|
|
* assigned by a KMC. The CSR address is expressed as bits <12:3>
|
|
* only. <17:13> are all set for IO page addresses and added by ucode.
|
|
* The DUP has 8 registers, so <2:1> must be zero. The other bits are
|
|
* reserved and must be zero.
|
|
*
|
|
* There is no way to release a line, short of re-starting the microcode.
|
|
*
|
|
*/
|
|
static void kmc_baseIn (int32 k, dupstate *d, uint16 cmdsel2, uint8 line) {
|
|
uint32 csraddress;
|
|
int32 dupidx;
|
|
|
|
/* Verify DUP is enabled and at specified address */
|
|
|
|
/* Ucode clears low three bits of CSR address, ors 1s into the high 3.
|
|
* CSR address here may include bits >17 (e.g. UBA number)
|
|
*
|
|
* The check for reserved bits is not done by the ucode, and can be
|
|
* removed. It's here for debugging any cases where this code is at fault.
|
|
*/
|
|
|
|
csraddress = sel6 & SEL6_II_DUPCSR;
|
|
|
|
if ((sel4 != 0) || (cmdsel2 & SEL2_II_RESERVED)) {
|
|
sim_debug (DF_ERR, &kmc_dev, "KMC%u: BASE IN reserved bits set\n", k);
|
|
kmc_halt (k, HALT_BADCSR);
|
|
return;
|
|
}
|
|
csraddress |= IOPAGEBASE;
|
|
|
|
dupidx = dup_csr_to_linenum (sel6);
|
|
if ((dupidx < 0) || (((size_t) dupidx) >= DIM(dupState))) { /* Call this a NXM so OS can recover */
|
|
sim_debug (DF_ERR, &kmc_dev, "KMC%u line %u: BASE IN %06o 0x%05x is not an enabled DUP\n",
|
|
k, line, csraddress, csraddress);
|
|
kmc_ctrlOut (k, SEL6_CO_NXM, 0, line, 0);
|
|
return;
|
|
}
|
|
d = &dupState[dupidx];
|
|
if ((d->kmc != -1) && (d->kmc != k)) {
|
|
sim_debug (DF_ERR, &kmc_dev, "KMC%u line %u: BASE IN %06o 0x%05x is already assigned to KMC%u\n",
|
|
k, line, csraddress, csraddress,
|
|
d->kmc);
|
|
kmc_ctrlOut (k, SEL6_CO_NXM, 0, line, 0);
|
|
return;
|
|
}
|
|
|
|
/* OK to take ownership.
|
|
* Master clear the DUP. NXM will cause a control-out.
|
|
*
|
|
* The microcode takes no special action to clear DTR.
|
|
*/
|
|
|
|
d->dupcsr = csraddress;
|
|
d->kmc = k;
|
|
line2dup[line] = d;
|
|
d->line = line;
|
|
|
|
/*
|
|
* Jumper W3 Installed causes RTS,DTR, and SecTxD to be cleared on Device Reset or bus init.
|
|
* Jumper W3 is installed in factory DUPs, and in the KS10 config.
|
|
* Make sure the DUP emulation enables this option.
|
|
*/
|
|
dup_set_W3_option (dupidx, 1);
|
|
/*
|
|
* Reset the DUP device. This will clear DTR and RTS.
|
|
*/
|
|
if (dup_reset_dup (dupidx) != SCPE_OK) {
|
|
sim_debug (DF_CTO, &kmc_dev, "KMC%u line %u: BASE IN dup %d DUP TXCSR NXM\n",
|
|
k, line, dupidx);
|
|
d->kmc = -1;
|
|
return;
|
|
}
|
|
|
|
/* Hardware requires a 2 usec delay before next access to DUP.
|
|
*/
|
|
|
|
d->dupidx = dupidx;
|
|
|
|
sim_debug (DF_INF, &kmc_dev, "KMC%u line %u: BASE IN DUP%u address=%06o 0x%05x assigned\n",
|
|
k, line, d->dupidx,csraddress, csraddress);
|
|
return;
|
|
}
|
|
|
|
/* Process CONTROL IN command
|
|
*
|
|
* CONTROL IN establishes the characteristics of each communication line
|
|
* controlled by the KMC. At least one CONTROL IN must be issued for each
|
|
* DUP that is to communicate.
|
|
*
|
|
* CONTROL IN writes the DUP CSRs to configure it for the selected mode.
|
|
*
|
|
* Not implemented:
|
|
* o Polling count (no mapping to emulator) (SEL4_CI_POLL)
|
|
*/
|
|
|
|
static void kmc_ctrlIn (int32 k, dupstate *d, int line) {
|
|
t_stat r;
|
|
|
|
if (DEBUG_PRS (&kmc_dev)) {
|
|
sim_debug (DF_CMD, &kmc_dev, "KMC%u line %u: CONTROL IN ", k, line);
|
|
if (!(sel6 & SEL6_CI_ENABLE)) {
|
|
sim_debug (DF_CMD, &kmc_dev, "line disabled\n");
|
|
} else {
|
|
sim_debug (DF_CMD, &kmc_dev, "enabled for %s in %s duplex",
|
|
(sel6 & SEL6_CI_DDCMP)? "DDCMP":"Bit-stuffing",
|
|
(sel6 & SEL6_CI_HDX)? "half" : "full");
|
|
if (sel6 & SEL6_CI_ENASS) {
|
|
sim_debug (DF_CMD, &kmc_dev, " SS:%u-%d",
|
|
(sel6 & SEL6_CI_SADDR), line);
|
|
}
|
|
sim_debug (DF_CMD, &kmc_dev, "\n");
|
|
}
|
|
}
|
|
|
|
/* BSEL4 has the polling count, which isn't needed for emulation */
|
|
|
|
d->linkstate &= ~(LINK_DSR|LINK_SEL);/* Initialize modem state reporting. */
|
|
|
|
d->ctrlFlags = sel6;
|
|
|
|
/* Initialize DUP interface in the appropriate mode
|
|
* DTR will be turned on.
|
|
*/
|
|
r = dup_setup_dup (d->dupidx, (sel6 & SEL6_CI_ENABLE),
|
|
(sel6 & SEL6_CI_DDCMP),
|
|
(sel6 & SEL6_CI_NOCRC),
|
|
(sel6 & SEL6_CI_HDX),
|
|
(sel6 & SEL6_CI_ENASS) ? (sel6 & SEL6_CI_SADDR) : 0);
|
|
|
|
/* If setup succeeded, enable packet callbacks.
|
|
*
|
|
* If setup failed, generate a CONTROL OUT.
|
|
*/
|
|
if (r == SCPE_OK) {
|
|
dup_set_callback_mode (d->dupidx, kdp_receive, kmc_txComplete, kmc_modemChange);
|
|
} else {
|
|
kmc_ctrlOut (k, SEL6_CO_NXM, 0, d->line, 0);
|
|
sim_debug (DF_CTO, &kmc_dev, "KMC%u line %u: CONTROL IN dup %d DUP CSR NXM\n",
|
|
k, line, d->dupidx);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* Process RX BUFFER IN command
|
|
*
|
|
* RX BUFFER IN delivers a buffer descriptor list from the host to
|
|
* the KMC. It may also deassign a buffer descriptor list.
|
|
*
|
|
* A buffer descriptor list is a sequential list of three word blocks in
|
|
* host memory space. Each 3-word block points to and describes the
|
|
* boundaries of a single buffer, which is also in host CPU memory.
|
|
*
|
|
* A buffer descriptor list is defined by its starting address. The
|
|
* end of a buffer descriptor list is marked in the list. A maximum of
|
|
* MAXQUEUE transmit and MAXQUEUE receive lists can be assigned to each
|
|
* COMM IOP-DUP communications line.
|
|
*
|
|
* The starting address of a buffer descriptor list must be word aligned.
|
|
*
|
|
* The buffers in this list will be filled with data received from the
|
|
* line and returned to the host by RX BUFFER OUT completions.
|
|
*
|
|
* Buffer descriptor lists are deassigned through use of the Kill bit
|
|
* and also reassigned when this bit is used in conjuntion with the
|
|
* Buffer Enable bit. When Kill is set, all buffer descriptor lists for
|
|
* the specified direction (RX or TX) are released. If the enable bit is
|
|
* also set, a new buffer descriptor list is assigned after the old lists
|
|
* are deassigned. In any case, RX kill places the associated DUP in sync
|
|
* search mode. TX kill brings the line to a mark hold state.
|
|
*
|
|
* Note that Buffer Enable is ignored unless Kill is also set.
|
|
*
|
|
*/
|
|
|
|
void kmc_rxBufferIn(dupstate *d, int32 ba, uint16 sel6v) {
|
|
int32 k = d->kmc;
|
|
BDL *qe;
|
|
uint32 bda = 0;
|
|
UNIT *rxup;
|
|
|
|
if (d->line == UNASSIGNED_LINE)
|
|
return;
|
|
|
|
ASSURE ((k >= 0) && (((unsigned int)k) < kmc_dev.numunits) && (d->dupidx != -1));
|
|
|
|
rxup = &rx_units[d->line][k];
|
|
|
|
if (!kmc_printBufferIn (k, &kmc_dev, d->line, TRUE, d->rxavail, ba, sel6v))
|
|
return;
|
|
|
|
if (sel6v & SEL6_BI_KILL) {
|
|
/* Kill all current RX buffers.
|
|
* Resync the DUP receiver.
|
|
*/
|
|
#ifdef DUP_RXRESYNC
|
|
dup_set_RCVEN (d->dupidx, FALSE);
|
|
dup_set_RCVEN (d->dupidx, TRUE);
|
|
#endif
|
|
if ((d->rxstate >= RXBUF) && (d->rxstate < RXFULL)) {
|
|
/* A bd is open (active).
|
|
* Updating bytes received should be done before the kill. TOPS-10 clears the UBA map
|
|
* before requesting it. But it doesn't look at bd. So don't report NXM.
|
|
* In these states, the bd is always cached.
|
|
*/
|
|
d->rx.bd[1] = d->rx.rcvc;
|
|
kmc_updateBDCount (d->rx.bda, d->rx.bd);
|
|
bda = d->rx.bda;
|
|
} else {
|
|
bda = 0;
|
|
}
|
|
d->rxstate = RXIDLE;
|
|
sim_cancel (rxup);
|
|
while ((qe = (BDL *)remqueue (d->rxqh.next, &d->rxavail)) != NULL) {
|
|
ASSURE (insqueue (&qe->hdr, d->bdqh.prev, &d->bdavail, DIM(d->bdq)));
|
|
}
|
|
if (!(sel6v & SEL6_BI_ENABLE)) {
|
|
kmc_ctrlOut (k, SEL6_CO_KDONE, SEL2_IOT, d->line, bda);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Add new buffer to available for RX queue */
|
|
|
|
if ((qe = (BDL *)remqueue (d->bdqh.next, &d->bdavail)) == NULL) {
|
|
sim_debug (DF_ERR, &kmc_dev, "KMC%u line %u: Too many receive buffers from hostd\n", k, d->line);
|
|
kmc_halt (k, HALT_RCVOVF);
|
|
return;
|
|
}
|
|
qe->ba = ba;
|
|
ASSURE (insqueue (&qe->hdr, d->rxqh.prev, &d->rxavail, MAXQUEUE));
|
|
|
|
if (sel6v & SEL6_BI_KILL) { /* KILL & Replace - ENABLE is set too */
|
|
kmc_ctrlOut (k, SEL6_CO_KDONE, SEL2_IOT, d->line, bda);
|
|
}
|
|
|
|
/* Start receiver if necessary */
|
|
|
|
if ((d->rxstate == RXIDLE) && !sim_is_active (rxup)) {
|
|
sim_activate_after (rxup, RXSTART_DELAY);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Message available callback
|
|
*
|
|
* The DUP calls this routine when a new message is available
|
|
* to be read.
|
|
*
|
|
* If the line's receive thread is idle, it is called to start the
|
|
* receive process. If the thread is busy, the message will be
|
|
* retrieved when the current receive process completes.
|
|
*
|
|
* This notification avoids the need for the receive thread to
|
|
* periodically poll for an incoming message when idle.
|
|
*
|
|
* The data and count arguments are unused - the function signature
|
|
* requires them for other modes.
|
|
*/
|
|
|
|
static void kdp_receive(int32 dupidx, int count) {
|
|
int32 k;
|
|
dupstate* d;
|
|
UNIT *rxup;
|
|
UNUSED_ARG (count);
|
|
|
|
ASSURE ((dupidx >= 0) && (((size_t)dupidx) < DIM(dupState)));
|
|
d = &dupState[dupidx];
|
|
ASSURE (dupidx == d->dupidx);
|
|
k = d->kmc;
|
|
rxup = &rx_units[d->line][k];
|
|
|
|
if (d->rxstate == RXIDLE){
|
|
sim_cancel (rxup);
|
|
sim_activate_after (rxup, RXNEWBD_DELAY);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Process TX BUFFER IN command
|
|
*
|
|
* TX BUFFER IN delivers a buffer descriptor list from the host to
|
|
* the KMC. It may also deassign a buffer descriptor list.
|
|
*
|
|
* For a complete description of buffer descriptor list, see
|
|
* RX BUFFER IN.
|
|
*
|
|
* The buffers in this list contain data to be transmitted to the
|
|
* line, and are returned to the host by TX BUFFER OUT completions.
|
|
*
|
|
* TX buffer descriptors include control flags that indicate start
|
|
* and end of message. These determine when the DUP is told start/
|
|
* stop accumulating data for and to insert CRCs. A resync flag
|
|
* indicates when sync characters must be inserted (after half-duplex
|
|
* line turn-around or CRC errors.)
|
|
*
|
|
*/
|
|
|
|
void kmc_txBufferIn(dupstate *d, int32 ba, uint16 sel6v) {
|
|
int32 k = d->kmc;
|
|
BDL *qe;
|
|
|
|
if (d->line == UNASSIGNED_LINE)
|
|
return;
|
|
|
|
ASSURE ((k >= 0) && (((unsigned int)k) < kmc_dev.numunits) && (d->dupidx != -1));
|
|
|
|
if (!kmc_printBufferIn (k, &kmc_dev, d->line, FALSE, d->txavail, ba, sel6v))
|
|
return;
|
|
|
|
if (sel6v & SEL6_BI_KILL) {
|
|
/* Kill all current TX buffers. The DUP can't abort transmission in simulation, so
|
|
* anything pending will stop when complete. The queue is reset here because
|
|
* the kill & replace option has to be able to enqueue the replacement BDL.
|
|
* If a tx is active, the DUP will issue a completion, which will report
|
|
* completion of the kill. A partial message that has been buffered, but
|
|
* not handed to the DUP can be stopped here.
|
|
*/
|
|
while ((qe = (BDL *)remqueue (d->txqh.next, &d->txavail)) != NULL) {
|
|
ASSURE (insqueue (&qe->hdr, d->bdqh.prev, &d->bdavail, DIM(d->bdq)));
|
|
}
|
|
if (d->txstate < TXACT) { /* DUP is idle */
|
|
sim_cancel (&tx_units[d->line][k]); /* Stop tx bdl walker */
|
|
d->txstate = TXIDLE;
|
|
|
|
if (!(sel6v & SEL6_BI_ENABLE)) {
|
|
kmc_ctrlOut (k, SEL6_CO_KDONE, 0, d->line, 0);
|
|
return;
|
|
}
|
|
/* Continue to replace buffer */
|
|
} else { /* DUP is transmitting, defer */
|
|
if (sel6v & SEL6_BI_ENABLE) /* Replace now, kill done later */
|
|
d->txstate = TXKILR;
|
|
else {
|
|
d->txstate = TXKILL;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(qe = (BDL *)remqueue (d->bdqh.next, &d->bdavail))) {
|
|
sim_debug (DF_ERR, &kmc_dev, "KMC%u line %u: Too many transmit buffers from host\n", k, d->line);
|
|
kmc_halt (k, HALT_XMTOVF);
|
|
return;
|
|
}
|
|
qe->ba = ba;
|
|
ASSURE (insqueue (&qe->hdr, d->txqh.prev, &d->txavail, MAXQUEUE));
|
|
if (d->txstate == TXIDLE) {
|
|
UNIT *txup = &tx_units[d->line][k];
|
|
if (!sim_is_active (txup)) {
|
|
txup->wait = TXSTART_DELAY;
|
|
sim_activate_after (txup, txup->wait);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* Transmit complete callback from the DUP
|
|
*
|
|
* Called when the last byte of a packet has been transmitted.
|
|
*
|
|
* Handle any deferred kill and schedule the next message.
|
|
*
|
|
* Note that this may be called from within the txService when the
|
|
* DUP is not speed limited. The unconditional activation ensures
|
|
* that txService will not be called recursively.
|
|
*/
|
|
|
|
static void kmc_txComplete (int32 dupidx, int status) {
|
|
dupstate *d;
|
|
UNIT *txup;
|
|
int32 k;
|
|
|
|
ASSURE ((dupidx >= 0) && (((size_t)dupidx) < DIM(dupState)));
|
|
|
|
d = &dupState[dupidx];
|
|
k = d->kmc;
|
|
txup = &tx_units[d->line][k];
|
|
|
|
if (status) { /* Failure is probably due to modem state change */
|
|
kmc_updateDSR(d); /* Change does not stop transmission or release buffer */
|
|
}
|
|
|
|
if (d->txstate < TXACT) { /* DUP shouldn't call when KMC is not sending */
|
|
sim_debug (DF_BUF, &kmc_dev, "KMC%u line %u: tx completion while inactive\n",
|
|
k, d->line);
|
|
return;
|
|
}
|
|
|
|
d->txmlen =
|
|
d->txslen = 0;
|
|
if ((d->txstate == TXKILL) || (d->txstate == TXKILR)) {
|
|
/* If DUP could kill a partial transmission, would update bd here */
|
|
d->txstate = TXDONE;
|
|
kmc_ctrlOut (k, SEL6_CO_KDONE, 0, d->line, d->tx.bda);
|
|
} else {
|
|
if (d->tx.bd[2] & BDL_LDS)
|
|
d->txstate = TXDONE;
|
|
else
|
|
d->txstate = TXSOM;
|
|
}
|
|
sim_cancel (txup);
|
|
sim_activate_after (txup, TXDONE_DELAY);
|
|
|
|
return;
|
|
}
|
|
|
|
/* Obtain a new buffer descriptor list from those queued by the host */
|
|
|
|
static t_bool kmc_txNewBdl(dupstate *d) {
|
|
BDL *qe;
|
|
|
|
if (!(qe = (BDL *)remqueue (d->txqh.next, &d->txavail))) {
|
|
return FALSE;
|
|
}
|
|
d->tx.bda = qe->ba;
|
|
ASSURE (insqueue (&qe->hdr, d->bdqh.prev, &d->bdavail, DIM(d->bdq)));
|
|
|
|
d->tx.first = TRUE;
|
|
d->tx.bd[1] = 0;
|
|
return kmc_txNewBd(d);
|
|
}
|
|
|
|
/* Obtain a new TX buffer descriptor.
|
|
*
|
|
* If the current descriptor is last, obtain a new list.
|
|
* (The new list case will recurse.)
|
|
*
|
|
*/
|
|
|
|
static t_bool kmc_txNewBd(dupstate *d) {
|
|
int32 k = d->kmc;
|
|
|
|
if (d->tx.first)
|
|
d->tx.first = FALSE;
|
|
else {
|
|
if (d->tx.bd[2] & BDL_LDS) {
|
|
if (!kmc_txNewBdl(d)) {
|
|
kmc_ctrlOut (k, SEL6_CO_TXU, 0, d->line, d->tx.bda);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
d->tx.bda += 6;
|
|
}
|
|
if (Map_ReadW (d->tx.bda, 2*3, d->tx.bd)) {
|
|
kmc_ctrlOut (k, SEL6_CO_NXM, 0, d->line, d->tx.bda);
|
|
return FALSE;
|
|
}
|
|
|
|
d->tx.ba = ((d->tx.bd[2] & BDL_XAD) << BDL_S_XAD) | d->tx.bd[0];
|
|
return TRUE;
|
|
}
|
|
|
|
/* Append data from a host buffer to the current message, as
|
|
* the DUP prefers to get the entire message in one swell foop.
|
|
*/
|
|
|
|
static t_bool kmc_txAppendBuffer(dupstate *d) {
|
|
int32 k = d->kmc;
|
|
uint16 rem;
|
|
|
|
if (!d->txmsg || (d->txmsize < d->txmlen+d->tx.bd[1])) {
|
|
d->txmsize = d->txmlen+d->tx.bd[1];
|
|
d->txmsg = (uint8 *)realloc(d->txmsg, d->txmsize);
|
|
ASSURE (d->txmsg);
|
|
}
|
|
rem = (uint16)Map_ReadB (d->tx.ba, d->tx.bd[1], d->txmsg+d->txmlen);
|
|
d->tx.bd[1] -= rem;
|
|
rem += (uint16)kmc_updateBDCount (d->tx.bda, d->tx.bd);
|
|
if (rem) {
|
|
kmc_ctrlOut (k, SEL6_CO_NXM, 0, d->line, d->tx.bda);
|
|
return FALSE;
|
|
}
|
|
d->txmlen += d->tx.bd[1];
|
|
return TRUE;
|
|
}
|
|
|
|
/* Try to deliver a completion (OUTPUT command)
|
|
*
|
|
* Because the same CSRs are used for delivering commands to the KMC,
|
|
* the RDO and RDI bits, along with RQI arbitrate access to the CSRs.
|
|
*
|
|
* The KMC prioritizes completions over taking new commands.
|
|
*
|
|
* Thus, if RDO is set, the host has not taken the previous completion
|
|
* data from the CSRs. Output is not possible until the host clears RDO.
|
|
*
|
|
* If RDO is clear, RDI indicates that the host owns the CSRs, and
|
|
* should be writing a command to them. Output is not possible.
|
|
*
|
|
* If neither is set, the KMC takes ownership of the CSRs and updates
|
|
* them from the queue before setting RDO.
|
|
*
|
|
* There is aditional prioitization of RDI/RDO in the logic that detects
|
|
* RDO clearing. If RQI has been set by the host before clearing RDO,
|
|
* the KMC guarantees that RDI will set even if more completions are
|
|
* pending.
|
|
*/
|
|
|
|
static void kmc_processCompletions (int32 k) {
|
|
CQ *qe;
|
|
|
|
if (sel2 & (SEL2_RDO | SEL2_RDI)) /* CSRs available? */
|
|
return;
|
|
|
|
if (!(qe = (CQ *)remqueue (cqueueHead.next, &cqueueCount))) {
|
|
return;
|
|
}
|
|
|
|
ASSURE (insqueue (&qe->hdr, freecqHead.prev, &freecqCount, CQUEUE_MAX));
|
|
sel2 = qe->bsel2;
|
|
sel4 = qe->bsel4;
|
|
sel6 = qe->bsel6;
|
|
|
|
sim_debug (DF_QUE, &kmc_dev, "KMC%u line %u: %s %s delivered: sel2=%06o sel4=%06o sel6=%06o\n",
|
|
k, ((sel2 & SEL2_LINE)>>SEL2_V_LINE),
|
|
(sel2 & SEL2_IOT)? "RX":"TX",
|
|
((sel2 & SEL2_CMD) == CMD_BUFFOUT)? "BUFFER OUT":"CONTROL OUT",
|
|
sel2, sel4, sel6);
|
|
|
|
sel2 |= SEL2_RDO;
|
|
|
|
kmc_updints (k);
|
|
|
|
return;
|
|
}
|
|
|
|
/* Queue a CONTROL OUT command to the host.
|
|
*
|
|
* All but one of these release one or more buffers to the host.
|
|
*
|
|
* code is the event (usually error)
|
|
* rx is TRUE for a receive buffer, false for transmit.
|
|
* line is the line number assigned by BASE IN
|
|
* bda is the address of the buffer descriptor that has been processed.
|
|
*
|
|
* Returns FALSE if the completion queue is full (a fatal error)
|
|
*/
|
|
|
|
static void kmc_ctrlOut (int32 k, uint8 code, uint16 rx, uint8 line, uint32 bda)
|
|
{
|
|
CQ *qe;
|
|
|
|
if (DEBUG_PRS (kmc_dev)) {
|
|
static const char *const codenames[] = {
|
|
"Undef", "Abort", "HCRC", "DCRC", "NoBfr", "DSR", "NXM", "TXU", "RXO", "KillDun" };
|
|
unsigned int idx = code;
|
|
idx = ((code < 06) || (code > 026))? 0: ((code/2)-2);
|
|
|
|
sim_debug (DF_CTO, &kmc_dev, "KMC%u line %u: %s CONTROL OUT Code=%02o (%s) Address=%06o\n",
|
|
k, line, rx? "RX":"TX", code, codenames[idx], bda);
|
|
}
|
|
|
|
if (!(qe = (CQ *)remqueue (freecqHead.next, &freecqCount))) {
|
|
sim_debug (DF_QUE, &kmc_dev, "KMC%u line %u: Completion queue overflow\n", k, line);
|
|
/* Set overflow status in last entry of queue */
|
|
qe = (CQ *)cqueueHead.prev;
|
|
qe->bsel2 |= SEL2_OVR;
|
|
return;
|
|
}
|
|
qe->bsel2 = ((line << SEL2_V_LINE) & SEL2_LINE) | rx | CMD_CTRLOUT;
|
|
qe->bsel4 = bda & 0177777;
|
|
qe->bsel6 = ((bda >> (16-SEL6_V_CO_XAD)) & SEL6_CO_XAD) | code;
|
|
ASSURE (insqueue (&qe->hdr, cqueueHead.prev, &cqueueCount, CQUEUE_MAX));
|
|
kmc_processCompletions(k);
|
|
return;
|
|
}
|
|
|
|
/* DUP device callback for modem state change.
|
|
* The DUP device provides this callback whenever
|
|
* any modem control signal changes state.
|
|
*
|
|
* The timing is not exact with respect to the data
|
|
* stream.
|
|
|
|
* This can be used for HDX as well as DSR CHANGE>
|
|
*
|
|
*/
|
|
static void kmc_modemChange (int32 dupidx) {
|
|
dupstate *d;
|
|
|
|
ASSURE ((dupidx >= 0) && (((size_t)dupidx) < DIM(dupState)));
|
|
d = &dupState[dupidx];
|
|
|
|
if (d->dupidx != -1) {
|
|
kmc_updateDSR (d);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Check for and report DSR changes to the host.
|
|
* DSR is assumed false initially by the host.
|
|
* DSR Change Control-Out reports each change.
|
|
* No value is provided; the report simply toggles
|
|
* the host's view of the state.
|
|
*
|
|
* This is the ONLY CONTROL OUT that does not release
|
|
* a buffer.
|
|
*
|
|
* Returns TRUE if a change occurred.
|
|
*/
|
|
static t_bool kmc_updateDSR (dupstate *d) {
|
|
int32 k = d->kmc;
|
|
int32 status;
|
|
|
|
status = dup_get_DSR(d->dupidx);
|
|
status = status? LINK_DSR : 0;
|
|
if (status ^ (d->linkstate & LINK_DSR)) {
|
|
d->linkstate = (d->linkstate &~LINK_DSR) | status;
|
|
kmc_ctrlOut (k, SEL6_CO_DSRCHG, 0, d->line, 0);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* Queue a BUFFER ADDRESS OUT command to the host.
|
|
*
|
|
* flags are applied to BSEL6 (e.g. receive EOM).
|
|
* rx is TRUE for a receive buffer, false for transmit.
|
|
* line is the line number assigned by BASE IN
|
|
* bda is the address of the buffer descriptor that has been processed.
|
|
*
|
|
* Returns FALSE if the completion queue is full (a fatal error)
|
|
*/
|
|
|
|
static t_bool kmc_bufferAddressOut (int32 k, uint16 flags, uint16 rx, uint8 line, uint32 bda) {
|
|
CQ *qe;
|
|
|
|
sim_debug (DF_BFO, &kmc_dev, "KMC%u line %u: %s BUFFER OUT Flags=%06o Address=%06o\n",
|
|
k, line, rx? "RX": "TX", flags, bda);
|
|
|
|
if (!kmc_printBDL(k, DF_BFO, &kmc_dev, line, bda, rx? 6: 2))
|
|
return FALSE;
|
|
if (!(qe = (CQ *)remqueue (freecqHead.next, &freecqCount))) {
|
|
sim_debug (DF_QUE, &kmc_dev, "KMC%u line %u: Completion queue overflow\n", k, line);
|
|
/* Set overflow status in last entry of queue */
|
|
qe = (CQ *)cqueueHead.prev;
|
|
qe->bsel2 |= SEL2_OVR;
|
|
return FALSE;
|
|
}
|
|
qe->bsel2 = ((line << SEL2_V_LINE) & SEL2_LINE) | rx | CMD_BUFFOUT;
|
|
qe->bsel4 = bda & 0177777;
|
|
qe->bsel6 = ((bda >> (16-SEL6_V_CO_XAD)) & SEL6_CO_XAD) | flags;
|
|
ASSURE (insqueue (&qe->hdr, cqueueHead.prev, &cqueueCount, CQUEUE_MAX));
|
|
|
|
kmc_processCompletions(k);
|
|
return TRUE;
|
|
}
|
|
|
|
/* The UBA does not do a RPW cycle when byte 0 (of 4)
|
|
* on a -10 is written. (It can, but the OS doesn't program it that
|
|
* way. Thus, if the count word is in the left half-word, updating
|
|
* it will trash the 3rd word of that buffer descriptor. The hardware
|
|
* works. The KMC microcode does a word NPR write. At this writing
|
|
* how the hardware works is a mystery. The UBA documentation is quite
|
|
* clear that a write is done; the prints show zeros muxed into the bits.
|
|
* The OS is NOT setting 'read reverse'. The KMC ucode is not doing
|
|
* any magic. And the OS will kill the KMC if the 3rd word of the BD
|
|
* is trashed. Unless there is an undiscovered ECO to the KS10, there
|
|
* is no explanation for the fact that the KDP does work on that machine.
|
|
* Certainly, if the UBA always did RPW cycles, this would work, and
|
|
* probably no one would notice the performance loss.
|
|
*
|
|
* For now, this code writes the count, and also the 3rd word if the
|
|
* count is in byte 0. It's not the right solution, but it works for now.
|
|
* This does an extra write if the UBA trashed the following word, and not
|
|
* otherwise. Note that any BDL with two buffers is guaranteed to
|
|
* run into this issue. If the first BD is in byte 0, its count is OK, but
|
|
* the following BD will start in byte 2, putting its count in byte 0 of
|
|
* the following word, causing the write to step on that bd's flags.
|
|
*/
|
|
|
|
static int32 kmc_updateBDCount(uint32 bda, uint16 *bd) {
|
|
|
|
return Map_WriteW (bda+2, (((bda+2) & 2)? 2 : 4), &bd[1]);
|
|
}
|
|
|
|
/* Halt a KMC. This happens for some errors that the real KMC
|
|
* may not detect, as well as when RUN is cleared.
|
|
* The kmc is halted & interrupts are disabled.
|
|
*/
|
|
|
|
static void kmc_halt (int32 k, int error) {
|
|
int line;
|
|
|
|
if (error){
|
|
sel0 &= ~(SEL0_IEO|SEL0_IEI);
|
|
}
|
|
sel0 &= ~SEL0_RUN;
|
|
|
|
kmc_updints (k);
|
|
for (line = 0; line < MAX_ACTIVE; line++) {
|
|
UNIT *up = &tx_units[line][k];
|
|
|
|
if (sim_is_active (up)) {
|
|
up->unit_htime = sim_activate_time (up);
|
|
sim_cancel (up);
|
|
} else {
|
|
up->unit_htime = 0;
|
|
}
|
|
up = &rx_units[line][k];
|
|
if (sim_is_active (up)) {
|
|
up->unit_htime = sim_activate_time (up);
|
|
sim_cancel (up);
|
|
} else {
|
|
up->unit_htime = 0;
|
|
}
|
|
}
|
|
sim_debug (DF_INF, &kmc_dev, "KMC%u: Halted at uPC %04o reason=%d\n", k, upc, error);
|
|
return;
|
|
}
|
|
/*
|
|
* Update interrupts pending.
|
|
*
|
|
* Since the interrupt request is shared across all KMCs
|
|
* (a simplification), pending flags are kept per KMC,
|
|
* and a global request count across KMCs for the UBA.
|
|
* The UBA will clear the global request flag when it grants
|
|
* an interrupt; thus for set the global flag is always set
|
|
* if this KMC has a request. This doesn't quite match "with IEI
|
|
* set, only one interrupt is generated for each setting of RDI."
|
|
* An extra interrupt, however, should be harmless.
|
|
*
|
|
* Since interrupts are generated by microcode, do not touch the interrupt
|
|
* system unless microcode initialization has run.
|
|
*/
|
|
|
|
static void kmc_updints(int32 k) {
|
|
if (!(gflags & FLG_UCINI)) {
|
|
return;
|
|
}
|
|
|
|
if ((sel0 & SEL0_IEI) && (sel2 & SEL2_RDI)) {
|
|
if (!(gflags & FLG_AINT)) {
|
|
sim_debug (DF_INT, &kmc_dev, "KMC%u: set input interrupt pending\n", k);
|
|
gflags |= FLG_AINT;
|
|
AintPending++;
|
|
}
|
|
SET_INT(KMCA);
|
|
} else {
|
|
if (gflags & FLG_AINT) {
|
|
sim_debug (DF_INT, &kmc_dev, "KMC%u: cleared pending input interrupt\n", k);
|
|
gflags &= ~FLG_AINT;
|
|
if (--AintPending == 0) {
|
|
CLR_INT(KMCA);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((sel0 & SEL0_IEO) && (sel2 & SEL2_RDO)) {
|
|
if (!(gflags & FLG_BINT)) {
|
|
sim_debug (DF_INT, &kmc_dev, "KMC%u: set output interrupt\n", k);
|
|
gflags |= FLG_BINT;
|
|
BintPending++;
|
|
}
|
|
SET_INT(KMCB);
|
|
} else {
|
|
if (gflags & FLG_BINT) {
|
|
sim_debug (DF_INT, &kmc_dev, "KKMC%u: clear output interrupt\n", k);
|
|
gflags &= ~FLG_BINT;
|
|
if (--BintPending == 0) {
|
|
CLR_INT(KMCB);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Interrupt acknowledge service.
|
|
* When the UBA grants an interrupt request, it
|
|
* requests the vector number from the device.
|
|
*
|
|
* These routines return the vector number from the
|
|
* interrupting KMC. Lower numbered KMCs have
|
|
* priority over higher numbered KMCs.
|
|
*
|
|
* Any one KMC should never have both input and output
|
|
* pending at the same time.
|
|
*/
|
|
|
|
static int32 kmc_AintAck (void) {
|
|
int32 vec = 0; /* no interrupt request active */
|
|
int32 k;
|
|
|
|
for (k = 0; ((size_t)k) < DIM (kmc_gflags); k++) {
|
|
if (gflags & FLG_AINT) {
|
|
vec = kmc_dib.vec + (k*010);
|
|
gflags &= ~FLG_AINT;
|
|
if (--AintPending == 0) {
|
|
CLR_INT(KMCA);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (vec)
|
|
sim_debug (DF_INT, &kmc_dev, "KMC%u input (A) interrupt ack vector %03o\n", k, vec);
|
|
else
|
|
sim_debug (DF_INT, &kmc_dev, "KMC input (A) passive release\n");
|
|
|
|
return vec;
|
|
}
|
|
|
|
static int32 kmc_BintAck (void) {
|
|
int32 vec = 0; /* no interrupt request active */
|
|
int32 k;
|
|
|
|
for (k = 0; ((size_t)k) < DIM (kmc_gflags); k++) {
|
|
if (gflags & FLG_BINT) {
|
|
vec = kmc_dib.vec + 4 + (k*010);
|
|
gflags &= ~FLG_BINT;
|
|
if (--BintPending == 0) {
|
|
CLR_INT(KMCB);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (vec)
|
|
sim_debug (DF_INT, &kmc_dev, "KMC%u output (B) interrupt ack vector %03o\n", k, vec);
|
|
else
|
|
sim_debug (DF_INT, &kmc_dev, "KMC output (B) passive release\n");
|
|
|
|
return vec;
|
|
}
|
|
|
|
/* Debug: Log a BUFFER IN or BUFFER OUT command.
|
|
* returns FALSE if print encounters a NXM as (a) it's fatal and
|
|
* (b) only one completion per bdl.
|
|
*/
|
|
|
|
static t_bool kmc_printBufferIn (int32 k, DEVICE *dev, uint8 line, t_bool rx,
|
|
int32 count, int32 ba, uint16 sel6v) {
|
|
t_bool kill = ((sel6v & (SEL6_BI_KILL|SEL6_BI_ENABLE)) == SEL6_BI_KILL);
|
|
const char *dir = rx? "RX": "TX";
|
|
|
|
sim_debug (DF_CMD, &kmc_dev, "KMC%u line %u: %s BUFFER IN%s %d, bdl=%06o 0x%04x\n", k, line, dir,
|
|
(kill? "(Buffer kill)": ((sel6v & SEL6_BI_KILL)? "(Kill & replace)": "")),
|
|
count, ba, ba);
|
|
|
|
if (kill) /* Just kill doesn't use BDL, may NXM if attempt to dump */
|
|
return TRUE;
|
|
|
|
/* Kill and replace supplies new BDL */
|
|
|
|
if (!kmc_printBDL(k, DF_CMD, dev, line, ba, rx? 5: 1))
|
|
return FALSE;
|
|
|
|
sim_debug (DF_QUE, &kmc_dev, "KMC%u line %u: %s BUFFER IN %d, bdl=%06o 0x%04X\n", k, line, dir, count, ba, ba);
|
|
|
|
return TRUE;
|
|
}
|
|
/*
|
|
* Debug: Dump a BDL and a sample of its buffer.
|
|
*
|
|
* prbuf: non-zero to access buffer
|
|
* Bit 1 set to print just one descriptor (BFO), clear to do entire list (BFI)
|
|
* Bit 2 set if rx (rx bfi doesn't print data)
|
|
*/
|
|
|
|
static t_bool kmc_printBDL(int32 k, uint32 dbits, DEVICE *dev, uint8 line, int32 ba, int prbuf) {
|
|
uint16 bd[3];
|
|
int32 dp;
|
|
|
|
if (!DEBUG_PRJ(dev,dbits))
|
|
return TRUE;
|
|
|
|
for (;;) {
|
|
if (Map_ReadW (ba, 3*2, bd) != 0) {
|
|
kmc_ctrlOut (k, SEL6_CO_NXM, 0, line, ba);
|
|
sim_debug (dbits,dev, "KMC%u line %u: NXM reading descriptor addr=%06o\n", k, line, ba);
|
|
return FALSE;
|
|
}
|
|
|
|
dp = bd[0] | ((bd[2] & BDL_XAD) << BDL_S_XAD);
|
|
sim_debug (dbits, dev, " bd[0] = %06o 0x%04X Adr=%06o\n", bd[0], bd[0], dp);
|
|
sim_debug (dbits, dev, " bd[1] = %06o 0x%04X Len=%u.\n", bd[1], bd[1], bd[1]);
|
|
sim_debug (dbits, dev, " bd[2] = %06o 0x%04X", bd[2], bd[2]);
|
|
if (bd[2] & BDL_LDS) {
|
|
sim_debug (dbits, dev, " Last");
|
|
}
|
|
if (bd[2] & BDL_RSY) {
|
|
sim_debug (dbits, dev, " Rsync");
|
|
}
|
|
if (bd[2] & BDL_EOM) {
|
|
sim_debug (dbits, dev, " XEOM");
|
|
}
|
|
if (bd[2] & BDL_SOM) {
|
|
sim_debug (dbits, dev, " XSOM");
|
|
}
|
|
sim_debug (dbits, dev, "\n");
|
|
|
|
if (prbuf) {
|
|
uint8 buf[20];
|
|
if (bd[1] > sizeof buf)
|
|
bd[1] = sizeof buf;
|
|
|
|
if (Map_ReadB (dp, bd[1], buf) != 0) {
|
|
kmc_ctrlOut (k, SEL6_CO_NXM, 0, line, dp);
|
|
sim_debug (dbits, dev, "KMC%u line %u: NXM reading buffer %06o\n", k, line, dp);
|
|
return FALSE;
|
|
}
|
|
|
|
if (prbuf != 5) { /* Don't print RX buffer in */
|
|
for (dp = 0; dp < bd[1] ; dp++) {
|
|
sim_debug (dbits, dev, " %02x", buf[dp]);
|
|
}
|
|
sim_debug (dbits, dev, "\r\n");
|
|
}
|
|
}
|
|
if ((bd[2] & BDL_LDS) || (prbuf & 2)) /* Last, or 1 only */
|
|
break;
|
|
ba += 6;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* Verify that the microcode image is one that this code knows how to emulate.
|
|
* As far as I know, there was COMM IOP-DUP V1.0 and one patch, V1.0A.
|
|
* This is the patched version, which I have verified by rebuilding the
|
|
* V1.0A microcode from sources and computing its CRC from the binary.
|
|
*
|
|
* RSX DECnet has a different binary; the version is unknown.
|
|
*
|
|
* The reason for this check is that there ARE other KMC microcodes.
|
|
* If some software thinks it's loading one of those, the results
|
|
* could be catastrophic - and hard to diagnose. (COMM IOP-DZ has
|
|
* a similar function; but there are other, stranger microcodes.
|
|
*/
|
|
|
|
static const char *kmc_verifyUcode (int32 k) {
|
|
size_t i, n;
|
|
uint16 crc = 'T' << 8 | 'L';
|
|
uint8 w[2];
|
|
static const struct {
|
|
uint16 crc;
|
|
const char *name;
|
|
} known[] = {
|
|
{ 0xc3cd, "COMM IOP-DUP V1.0A" },
|
|
{ 0x1a38, "COMM IOP-DUP RSX" },
|
|
};
|
|
|
|
for (i = 0, n = 0; i < DIM (ucode); i++) {
|
|
if (ucode[i] != 0)
|
|
n++;
|
|
w[0] = ucode[i] >> 8;
|
|
w[1] = ucode[i] & 0xFF;
|
|
crc = ddcmp_crc16 (crc, &w, sizeof w);
|
|
}
|
|
if (n < ((3 * DIM (ucode))/4)) {
|
|
sim_debug (DF_INF, &kmc_dev, "KMC%u: Microcode not loaded\n", k);
|
|
return NULL;
|
|
}
|
|
for (i = 0; i < DIM (known); i++) {
|
|
if (crc == known[i].crc) {
|
|
sim_debug (DF_INF, &kmc_dev, "KMC%u: %s microcode loaded\n", k, known[i].name);
|
|
return known[i].name;
|
|
}
|
|
}
|
|
sim_debug (DF_INF, &kmc_dev, "KMC%u: Unknown microcode loaded\n", k);
|
|
return NULL;
|
|
}
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
static void initqueue (QH *head, int32 *count, int32 max, void *list, size_t size) {
|
|
head->next = head->prev = head;
|
|
*count = 0;
|
|
if (list == NULL)
|
|
return;
|
|
while (insqueue ((QH *)list, head->prev, count, max))
|
|
list = (QH *)(((char *)list)+size);
|
|
return;
|
|
}
|
|
|
|
/* 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.pred
|
|
*
|
|
* returns FALSE if queue is full.
|
|
*/
|
|
|
|
static t_bool insqueue (QH *entry, QH *pred, int32 *count, int32 max) {
|
|
if (*count >= max)
|
|
return FALSE;
|
|
entry-> next = pred->next;
|
|
entry->prev = pred;
|
|
pred->next->prev = entry;
|
|
pred->next = entry;
|
|
++*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.pred.
|
|
*
|
|
* returns FALSE if queue is empty.
|
|
*/
|
|
|
|
static void *remqueue (QH *entry, int32 *count) {
|
|
if (*count <= 0)
|
|
return NULL;
|
|
entry->prev->next = entry->next;
|
|
entry->next->prev = entry->prev;
|
|
--*count;
|
|
return (void *)entry;
|
|
}
|
|
|
|
/* Simulator UI functions */
|
|
|
|
/* SET KMC DEVICES processor
|
|
*
|
|
* Adjusts the size of I/O space and number of vectors to match the specified
|
|
* number of KMCs.
|
|
* The txup is that of unit zero.
|
|
*/
|
|
|
|
#if KMC_UNITS > 1
|
|
static t_stat kmc_setDeviceCount (UNIT *txup, int32 val, CONST char *cptr, void *desc) {
|
|
int32 newln;
|
|
uint32 dupidx;
|
|
t_stat r;
|
|
DEVICE *dptr = find_dev_from_unit(txup);
|
|
|
|
if (cptr == NULL)
|
|
return SCPE_ARG;
|
|
|
|
for (dupidx = 0; dupidx < DIM (dupState); dupidx++) {
|
|
dupstate *d = &dupState[dupidx];
|
|
if ((d->kmc != -1) || (d->dupidx != -1)) {
|
|
return SCPE_ALATT;
|
|
}
|
|
}
|
|
newln = (int32) get_uint (cptr, 10, KMC_UNITS, &r);
|
|
if ((r != SCPE_OK) || (newln == (int32)dptr->numunits))
|
|
return r;
|
|
if (newln == 0)
|
|
return SCPE_ARG;
|
|
kmc_dib.lnt = newln * IOLN_KMC; /* set length */
|
|
kmc_dib.vnum = newln * 2; /* set vectors */
|
|
dptr->numunits = newln;
|
|
return kmc_reset (dptr); /* setup devices and auto config */
|
|
}
|
|
#endif
|
|
|
|
/* Report number of configured KMCs */
|
|
|
|
#if KMC_UNITS > 1
|
|
static t_stat kmc_showDeviceCount (FILE *st, UNIT *txup, int32 val, CONST void *desc) {
|
|
DEVICE *dev = find_dev_from_unit(txup);
|
|
|
|
if (dev->flags & DEV_DIS) {
|
|
fprintf (st, "Disabled");
|
|
return SCPE_OK;
|
|
}
|
|
|
|
fprintf (st, "devices=%d", dev->numunits);
|
|
return SCPE_OK;
|
|
}
|
|
#endif
|
|
|
|
/* Set line speed
|
|
*
|
|
* This is the speed at which the KMC processed data for/from a DUP.
|
|
* This limits the rate at which buffer descriptors are processed, even
|
|
* if the actual DUP speed is faster. If the DUP speed is slower, of
|
|
* course the DUP wins. This limit ensures that the infinite speed DUPs
|
|
* of simulation won't overrun the host's ability to process buffers.
|
|
*
|
|
* The speed range is expressed as line bits/sec for user convenience.
|
|
* The limits are 300 bps (a sync line won't realistically run slower than
|
|
* that), and 1,000,000 bps - the maximum speed of a DMR11. The KDP's
|
|
* practical limit was about 19,200 BPS. The higher limit is a rate that
|
|
* the typicaly host software could handle, even if the line couldn't.
|
|
*
|
|
* Note that the DUP line speed can also be set.
|
|
*
|
|
* To allow setting the speed before a DUP has been assigned to a KMC by
|
|
* the OS, the set (and show) commands reference the DUP and apply to any
|
|
* potential use of that DUP by a KMC.
|
|
*/
|
|
|
|
static t_stat kmc_setLineSpeed (UNIT *txup, int32 val, CONST char *cptr, void *desc) {
|
|
dupstate *d;
|
|
int32 dupidx, newspeed;
|
|
char gbuf[CBUFSIZE];
|
|
t_stat r;
|
|
|
|
if (!cptr || !*cptr)
|
|
return SCPE_ARG;
|
|
|
|
cptr = get_glyph (cptr, gbuf, '='); /* get next glyph */
|
|
if (*cptr == 0) /* should be speed */
|
|
return SCPE_2FARG;
|
|
dupidx = (int32) get_uint (gbuf, 10, DUP_LINES, &r); /* Parse dup # */
|
|
if ((r != SCPE_OK) || (dupidx < 0)) /* error? */
|
|
return SCPE_ARG;
|
|
|
|
cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */
|
|
if (*cptr != 0) /* should be end */
|
|
return SCPE_2MARG;
|
|
cptr = gbuf;
|
|
if (!strcmp (cptr, "DUP"))
|
|
cptr += 3;
|
|
newspeed = (int32) get_uint (cptr, 10, MAX_SPEED, &r);
|
|
if ((r != SCPE_OK) || (newspeed < 300)) /* error? */
|
|
return SCPE_ARG;
|
|
|
|
d = &dupState[dupidx];
|
|
d->linespeed = newspeed;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat kmc_showLineSpeed (FILE *st, UNIT *txup, int32 val, CONST void *desc) {
|
|
int dupidx;
|
|
|
|
fprintf (st, "DUP KMC Line Speed\n"
|
|
"--- --- ---- --------\n");
|
|
|
|
for (dupidx =0; dupidx < DUP_LINES; dupidx++) {
|
|
dupstate *d = &dupState[dupidx];
|
|
|
|
fprintf (st, "%3u ", dupidx);
|
|
|
|
if (d->kmc == -1) {
|
|
fprintf (st, " - - ");
|
|
} else {
|
|
fprintf (st, "%3u %3u", d->kmc, d->line);
|
|
}
|
|
fprintf (st, " %8u\n", d->linespeed);
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Show KMC status */
|
|
|
|
t_stat kmc_showStatus (FILE *st, UNIT *up, int32 v, CONST void *dp) {
|
|
int32 k = up->unit_kmc;
|
|
int32 line;
|
|
t_bool first = TRUE;
|
|
DEVICE *dev = find_dev_from_unit(up);
|
|
const char *ucname;
|
|
|
|
if ((dev->flags & DEV_DIS) || (((uint32)k) >= dev->numunits)) {
|
|
fprintf (st, "KMC%u Disabled\n", k);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
ucname = kmc_verifyUcode (k);
|
|
|
|
fprintf (st, "KMC%u ", k);
|
|
|
|
if (!(sel0 & SEL0_RUN)) {
|
|
fprintf (st, "%s halted at uPC %04o\n",
|
|
(ucname?ucname: "(No or unknown microcode)"), upc);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
fprintf (st, "%s is running at uPC %04o\n",
|
|
(ucname?ucname: "(No or unknown microcode)"), upc);
|
|
|
|
if (!(gflags & FLG_UCINI)) {
|
|
return SCPE_OK;
|
|
}
|
|
|
|
for (line = 0; line <= MAX_LINE; line++) {
|
|
dupstate *d = line2dup[line];
|
|
if (d->kmc == k) {
|
|
if (first) {
|
|
fprintf (st, " Line DUP CSR State\n");
|
|
first = FALSE;
|
|
}
|
|
fprintf (st, " %3u %3u %06o %-8s %3s %s %s %s",
|
|
line, d->dupidx, d->dupcsr,
|
|
(d->ctrlFlags & SEL6_CI_ENABLE)? "enabled": "disabled",
|
|
(d->linkstate & LINK_DSR)? "DSR" : "OFF",
|
|
(d->ctrlFlags & SEL6_CI_DDCMP)? "DDCMP" : "Bit-Stuff",
|
|
(d->ctrlFlags & SEL6_CI_HDX)? "HDX " : "FDX",
|
|
(d->ctrlFlags & SEL6_CI_NOCRC)? "NOCRC": "");
|
|
if (d->ctrlFlags & SEL6_CI_ENASS)
|
|
fprintf (st, " SS (%u) ", d->ctrlFlags & SEL6_CI_SADDR);
|
|
fprintf (st, "\n");
|
|
}
|
|
}
|
|
if (first)
|
|
fprintf (st, " No DUPs assigned\n");
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Help for this device.
|
|
*
|
|
*/
|
|
|
|
static t_stat kmc_help (FILE *st, DEVICE *dptr,
|
|
UNIT *uptr, int32 flag, const char *cptr) {
|
|
const char *const text =
|
|
" The KMC11-A is a general purpose microprocessor that is used in\n"
|
|
" several DEC products. The KDP is an emulation of one of those\n"
|
|
" products: COMM IOP-DUP.\n"
|
|
"\n"
|
|
" The COMM IOP-DUP microcode controls and supervises 1 - 16 DUP-11\n"
|
|
" synchronous communications line interfaces, providing scatter/gather\n"
|
|
" DMA, message framing, modem control, CRC validation, receiver\n"
|
|
" resynchronization, and address recognition.\n"
|
|
"\n"
|
|
" The DUP-11 lines are assigned to the KMC11 by the (emulated) operating\n"
|
|
" system, but SimH must be told how to connect them. See the DUP HELP\n"
|
|
" for details.\n"
|
|
"1 Hardware Description\n"
|
|
" The KMC11-A microprocessor is a 16-bit Harvard architecture machine\n"
|
|
" optimized for data movement, character processing, address arithmetic\n"
|
|
" and other functions necessary for controlling I/O devices. It resides\n"
|
|
" on the UNIBUS and operates in parallel with the host CPU with a cycle\n"
|
|
" time of 300 nsec. It contains a 1024 word writable control store that\n"
|
|
" is loaded by the host, 1024 words of data memory, 16 8-bit scratchpad\n"
|
|
" registers, and 8 bytes of RAM that are dual-ported between the KMC11\n"
|
|
" and UNIBUS I/O space. It also has a timer and various internal busses\n"
|
|
" and registers.\n"
|
|
"\n"
|
|
" Seven of the eight bytes of dual-ported RAM have no fixed function;\n"
|
|
" they are defined by the microcode. The eighth register allows the\n"
|
|
" host to control the KMC11: the host can start, stop, examine state and\n"
|
|
" load microcode using this register.\n"
|
|
"\n"
|
|
" The microprocessor is capable of initiating DMA (NPR) UNIBUS cycles to\n"
|
|
" any UNIBUS address (memory and I/O space). It can interrupt the host\n"
|
|
" via one of two interrupt vectors.\n"
|
|
"\n"
|
|
" The microcodes operate other UNIBUS devices by reading and writing\n"
|
|
" their CSRs with UNIBUS DMA transactions, typically on a\n"
|
|
" character-by-character basis. There is no direct connection between\n"
|
|
" the KMC11 and the peripherals that it controls. The controlled\n"
|
|
" devices do not generate interrupts; all interrupts are generated by\n"
|
|
" the KMC11, which monitors the devices by polling their CSRs.\n"
|
|
"\n"
|
|
" By presenting the character-oriented peripherals to the host as\n"
|
|
" message-oriented devices, the KMC11 reduces the host's overhead in\n"
|
|
" operating the peripherals, relaxes the required interrupt response\n"
|
|
" times and increases the potential I/O throughput of a system.\n"
|
|
"\n"
|
|
" The hardware also has a private bus that can be used to control\n"
|
|
" dedicated peripherals (such as a DMC11 synchronous line unit) without\n"
|
|
" UNIBUS transactions. This feature is not emulated.\n"
|
|
"\n"
|
|
" This emulation does not execute the KMC microcode, but rather provides\n"
|
|
" a functional emulation.\n"
|
|
"\n"
|
|
" However, some of the microcode operators are emulated because system\n"
|
|
" loaders and OS diagnostics execute single instructions to initialize\n"
|
|
" or diagnose the device.\n"
|
|
"2 $Registers\n"
|
|
"2 Related devices\n"
|
|
" Other versions of the KMC11 have ROM microcode, which are used in such\n"
|
|
" devices as the DMC11 and DMR11 communications devices. This emulation\n"
|
|
" does not support those versions.\n"
|
|
"\n"
|
|
" Microcodes, not supported by this emulation, exist which control other\n"
|
|
" UNIBUS peripherals in a similar manner. These include:\n"
|
|
"\n"
|
|
"+DMA for DZ11 asynchronous lines (COMM IOP-DZ)\n"
|
|
"+DMA for line printers\n"
|
|
"+Arpanet IMP interface (AN22 on the KS10/TOPS-20)\n"
|
|
"\n"
|
|
" The KMC11 was also embedded in other products, such as the DX20 Massbus\n"
|
|
" to IBM channel adapter.\n"
|
|
"\n"
|
|
" The KMC11-B is an enhanced version of the KMC11-A. Note that microcode\n"
|
|
" loading is handled differently in that version, which is NOT emulated.\n"
|
|
"1 Configuration\n"
|
|
" Most configuration of KDP lines is done by the host OS and by SimH\n"
|
|
" configuration of the DUP11 lines.\n"
|
|
"\n"
|
|
#if KMC_TROLL
|
|
" The KDP has two configurable parameters.\n"
|
|
#else
|
|
" The KDP has one configurable parameter.\n"
|
|
#endif
|
|
" Line speed - this is the speed at which each communication line\n"
|
|
" operates. The DUP11's line speed should be set to 'unlimited' to\n"
|
|
" avoid unpredictable interactions.\n"
|
|
#if KMC_TROLL
|
|
" Troll - the KDP emulation includes a process that will intentionally\n"
|
|
" drop or corrupt some messages. This emulates the less-than-perfect\n"
|
|
" communications lines encountered in the real world, and enables\n"
|
|
" network monitoring software to see non-zero error counters.\n"
|
|
"\n"
|
|
" The troll selects messages with a probablility selected by the SET\n"
|
|
" TROLL 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.\n"
|
|
#endif
|
|
"2 $Set commands\n"
|
|
#if KMC_UNITS > 1
|
|
" SET KDP DEVICES=n enables emulation of up to %1s KMC11s.\n"
|
|
#endif
|
|
"2 $Show commands\n"
|
|
"1 Operation\n"
|
|
" A KDP device consists of one or more DUP11s controlled by a KMC11.\n"
|
|
" The association of DUP11s to KMC11s is determined by the host OS.\n"
|
|
"\n"
|
|
" For RSX DECnet, use NCP:\n"
|
|
" +SET LINE KDP-kdp-line CSR address\n"
|
|
" +SET LINE KDP-kdp-line UNIT CSR address\n"
|
|
" where 'kdp' is the KDP number and 'line' is the line number on\n"
|
|
" that kdp. 'address' is the I/O page offset of the CSR; e.g.\n"
|
|
" 760050 is entered as 160050.\n"
|
|
"\n"
|
|
" For TOPS-10/20, the addresses are fixed.\n"
|
|
"\n"
|
|
" For VMS...\n"
|
|
"\n"
|
|
" Although the microcode is not directly executed by the emulated KMC11,\n"
|
|
" the correct microcode must be loaded by the host operating system.\n"
|
|
;
|
|
char kmc_units[10];
|
|
|
|
sprintf (kmc_units, "%u", KMC_UNITS);
|
|
|
|
return scp_help (st, dptr, uptr, flag, text, cptr, kmc_units);
|
|
}
|
|
|
|
/* Description of this device.
|
|
* Conventionally last function in the file.
|
|
*/
|
|
static const char *kmc_description (DEVICE *dptr) {
|
|
if (dptr == &kmc_dev)
|
|
return "KMC11-A Synchronous line controller supporting only COMM IOP/DUP microcode";
|
|
else
|
|
return "KMC pseudo device for receive units";
|
|
}
|