| /* 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 <assert.h> | |
| #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, char *cptr, void *desc); | |
| static t_stat kmc_showDeviceCount (FILE *st, UNIT *txup, int32 val, void *desc); | |
| #endif | |
| static t_stat kmc_setLineSpeed (UNIT *txup, int32 val, char *cptr, void *desc); | |
| static t_stat kmc_showLineSpeed (FILE *st, UNIT *txup, int32 val, void *desc); | |
| static t_stat kmc_showStatus (FILE *st, UNIT *up, int32 v, 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 */ | |
| /* 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, 010, "ADDRESS", "ADDRESS", | |
| &set_addr, &show_addr, NULL, "Bus address" }, | |
| { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "VECTOR", "ADDRESS", | |
| &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; | |
| } | |
| } | |
| 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; | |
| assert ((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; | |
| assert (d->txmsg[d->txslen + 0] != DDCMP_ENQ); | |
| assert (((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 */ | |
| assert ((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; | |
| } | |
| assert (((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) { | |
| assert (!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; | |
| assert ((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]; | |
| } | |
| assert (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); | |
| assert (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: | |
| assert (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; | |
| assert ((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); | |
| assert ((dupidx >= 0) && (((size_t)dupidx) < DIM(dupState))); | |
| d = &dupState[dupidx]; | |
| assert (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; | |
| assert ((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; | |
| assert ((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); | |
| assert (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; | |
| assert ((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, 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, 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, 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, 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, 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) { | |
| return "KMC11-A Synchronous line controller supporting only COMM IOP/DUP microcode"; | |
| } |