1
0
mirror of https://github.com/simh/simh.git synced 2026-03-03 01:58:53 +00:00
Files
simh.simh/PDP11/pdp11_kmc.c
2013-07-05 00:54:05 -04:00

2228 lines
73 KiB
C

/* pdp11_kmc.c: KMC11-A with COMM IOP-DUP microcode Emulation
------------------------------------------------------------------------------
Written 2002 by Johnny Eriksson <bygg@stacken.kth.se>
Adapted to SIMH 3.? by Robert M. A. Jarratt in 2013
Enhanced, and largely rewritten by Timothe Litt <litt@acm.org> in 2013,
building on previous work.
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 are happy.
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:
*
* We don't do anything but full-duplex DDCMP.
* TODO marks implementation issues.
*
* The DUP packet API complicates things, making completions and buffer flush
* occurr at inopportune times.
* Move descriptor walks to service routine.
*
*/
/* The KMC11-A is a general purpose microprocessor that is used in several
* DEC products. It came in versions that had ROM microcode, which were
* used in such devices as the DMC1 and DMR11 communications devices.
*
* In each case, it is a Unibus master and operates under OS control as
* an asynchronous IO processor. In some versions, it uses a private
* communications bus to an associated unit, typically a line card. In
* others, it controls some other device over the Unibus, accessing the
* slave devices CSRs as though it were a CPU doing polled IO.
*
* It also was produced with RAM microcode, and served as a general-purpose
* DMA engine. Microcodes exist for this version to allow it to:
* * Provide DMA for DZ11 asynchronous lines (COMM IOP-DZ)
* * Provide DMA for line printers
* * Provide DMA and message framing for DUP-11 sync lines (COMM IOP-DUP)
*
* The device was also embedded in other products, such as the DX20 Massbus
* to IBM channel adapter.
*
* This is an emulation of one of those products: COMM IOP-DUP. It does
* not execute the KMC microcode, but rather provides a functional emulation.
*
* Some of the microcode operators are emulated because system loaders and
* OS diagnostics would execute single instructions to initialize or dump
* the device.
*
* The KMC11-B is an enhanced version of the KMC11-A. Note that microcode
* loading is handled differently in that version.
*/
#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]))
/* 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;
static char *kmc_description (DEVICE *dptr);
extern int32 IREQ (HLVL);
/* 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 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_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 16 /* Number of rx bdl's we can handle. */
/* 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
/* TODO: Remove 4 debug lines */
#undef KMC_UNITS
#define KMC_UNITS 2
#undef INITIAL_KMCS
#define INITIAL_KMCS 2
/* TODO: adjust this */
/* Interval at which to run service task for each DUP
* 417 uSec is roughly the byte rate at 19,200 b/sec
*/
#ifndef KMC_POLLTIME
#define KMC_POLLTIME 417
#endif
struct buffer_list { /* BDL queue elements */
QH hdr;
uint32 ba;
};
typedef struct buffer_list BDL;
struct workblock {
struct dupstate *dup;
t_bool first;
uint32 bda;
uint16 bd[3];
uint16 rcvc;
#define xmtc 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 */
int32 line; /* OS-assigned line number */
int32 dupidx; /* DUP API Number amongst all DUP11's on Unibus (-1 == unassigned) */
int32 modemstate; /* Line Link Status (i.e. 1 when DCD/DSR is on, 0 otherwise */
#define MDM_DSR 1
uint16 ctrlFlags;
uint32 dupcsr;
BDL bdq[MAXQUEUE*2]; /* Queued TX and RX buffer lists */
QH bdqh; /* Free queue */
int32 bdavail;
QH rxqh; /* Receive queue from host */
int32 rxavail;
QH txqh; /* Transmit queue form host */
int32 txavail;
WB tx;
uint32 txstate;
#define TXIDLE 0
#define TXSOM 1
#define TXHDR 2
#define TXDATA 4
#define TXACT 5
#define TXKILL 6
#define TXKILR 7
uint8 *txmsg;
size_t txmsize, txmlen;
};
typedef struct dupstate dupstate;
/* State for every DUP that MIGHT be controlled.
* A DUP can be controlled by at most one KMC.
*/
dupstate dupState[DUP_LINES] = { 0 };
/* Flags defining sim_debug conditions. */
#define DF_CMD 0001 /* Print commands. */
#define DF_TX 0002 /* Print tx done. */
#define DF_RX 0004 /* Print rx done. */
#define DF_DATA 0010 /* Print data. */
#define DF_QUEUE 0020 /* Print rx/tx queue changes. */
#define DF_TRC 0040 /* Detailed trace. */
#define DF_INF 0100 /* Info */
DEBTAB kmc_debug[] = {
{"CMD", DF_CMD},
{"TX", DF_TX},
{"RX", DF_RX},
{"DATA", DF_DATA},
{"QUEUE", DF_QUEUE},
{"TRC", DF_TRC},
{"INF", DF_INF},
{0}
};
/* These count the total pending interrupts for each vector
* across all KMCs.
*/
int32 AintPending = 0;
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. E.g. given a UNIT pointer,
* k = uptr - kc_units.
* The KMC assigned to control a DUP is stored it its dupstate.
* k = dupState[ndupno]->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
*
*/
/* 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_BADDUP 6 /* DUP not configured and enabled */
#define HALT_DUPALC 7 /* DUP assigned to another KMC */
#define HALT_RCVOVF 8 /* Too many receive buffers assigned */
#define HALT_MTRCV 9 /* Receive buffer descriptor has zero size */
#define HALT_XMTOVF 10 /* Too many transmit buffers assigned */
#define HALT_XSOM 11 /* Transmission didn't start with SOM */
#define HALT_XSOM2 12 /* Data buffer didn't start with SOM */
#define HALT_BADUC 13 /* 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 */
uint16 kmc_sel0[KMC_UNITS]; /* CSR0 - BSEL 1,0 */
#define sel0 kmc_sel0[k]
uint16 kmc_sel2[KMC_UNITS]; /* CSR2 - BSEL 3,2 */
#define sel2 kmc_sel2[k]
uint16 kmc_sel4[KMC_UNITS]; /* CSR4 - BSEL 5,4 */
#define sel4 kmc_sel4[k]
uint16 kmc_sel6[KMC_UNITS]; /* CSR6 - BSEL 7,6 */
#define sel6 kmc_sel6[k]
/* Microprocessor state - subset exposed to the host */
uint16 kmc_upc[KMC_UNITS]; /* Micro PC */
#define upc kmc_upc[k]
uint16 kmc_mar[KMC_UNITS]; /* Micro Memory Address Register */
#define mar kmc_mar[k]
uint16 kmc_mna[KMC_UNITS]; /* Maintenance Address Register */
#define mna kmc_mna[k]
uint16 kmc_mni[KMC_UNITS]; /* Maintenance Instruction Register */
#define mni kmc_mni[k]
uint16 kmc_ucode[KMC_UNITS][KMC_CRAMSIZE];
#define ucode kmc_ucode[k]
uint16 kmc_dram[KMC_UNITS][KMC_DRAMSIZE];
#define dram kmc_dram[k]
dupstate *kmc_line2dup[KMC_UNITS][MAX_LINE+1];
#define line2dup kmc_line2dup[k]
/* General state booleans */
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 */
/* Completion queue elements, header and freelist */
CQ kmc_cqueue[KMC_UNITS][CQUEUE_MAX];
#define cqueue kmc_cqueue[k]
QH kmc_cqueueHead[KMC_UNITS];
#define cqueueHead kmc_cqueueHead[k]
int32 kmc_cqueueCount[KMC_UNITS];
#define cqueueCount kmc_cqueueCount[k]
QH kmc_freecqHead[KMC_UNITS];
#define freecqHead kmc_freecqHead[k]
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_eventService(UNIT * uptr);
static t_stat kmc_setDeviceCount (UNIT *uptr, int32 val, char *cptr, void *desc);
static t_stat kmc_showDeviceCount (FILE *st, UNIT *uptr, int32 val, void *desc);
static t_stat kmc_showStatus ( FILE *st, UNIT *up, int32 v, void *dp);
/* Global data */
static int32 kmc_AintAck (void);
static int32 kmc_BintAck (void);
#define IOLN_KMC 010
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} };
UNIT kmc_units[KMC_UNITS];
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
};
BITFIELD kmc_sel2_decoder[] = {
BITF (CMD,2),
BIT (IOT),
BITNCF (1),
BIT (RDI),
BITNCF (2),
BIT (RDO),
BITFFMT (LINE,7,"%u"),
BIT (CQOVF),
ENDBITS
};
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_VUN, 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",
kmc_units,
kmc_reg, /* Register decode tables */
kmc_mod, /* Modifier table */
KMC_UNITS, /* 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 | DEV_DIS /* Flags */
| DEV_DISABLE
| DEV_DEBUG,
0, /* debug control */
kmc_debug, /* debug flag table */
NULL, /* memory size routine */
NULL, /* logical name */
NULL, /* help routine */
NULL, /* attach help routine */
NULL, /* help context */
&kmc_description /* Device description routine */
};
/* Forward declarations: not referenced in simulator data */
static void kmc_masterClear(UNIT *uptr);
static void kmc_startUcode (uint32 k);
static void kmc_dispatchInputCmd(int32 k);
/* Control functions */
static void kmc_baseIn (int32 k, dupstate *d, uint16 cmdsel2, int line, uint32 ba );
static void kmc_ctrlIn (int32 k, dupstate *d, int line, uint32 ba );
/* Receive functions */
void kmc_rxBufferIn(dupstate *d, int32 ba, uint32 sel6v);
static void kdp_receive(int dupidx, uint8* data, int count);
static t_bool kmc_rxWriteViaBDL( WB *wb, uint8 *data, int count );
/* Transmit functions */
static void kmc_txBufferIn(dupstate *d, int32 ba, uint32 sel6v);
static void kmc_txStartDup(dupstate *d);
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);
/* Debug support */
static t_bool kmc_printBufferIn (int32 k, DEVICE *dev, int32 line, char *dir,
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) {
UNIT *uptr = dptr->units;
uint32 k, dupidx;
if (sim_switches & SWMASK ('P')) {
for (dupidx = 0; dupidx < DIM (dupState); dupidx++) {
dupstate *d = &dupState[dupidx];
d->kmc = -1;
d->dupidx = -1;
}
}
for (k = 0; k < KMC_UNITS; k++, uptr++) {
sim_debug(DF_INF, dptr, "KMC%d: Reset\n", k);
/* One-time initialization of UNIT */
if( !uptr->action ) {
memset (uptr, 0, sizeof *uptr);
uptr->action = &kmc_eventService;
uptr->flags = 0;
uptr->capac = 0;
}
kmc_masterClear (uptr); /* 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 */
}
/*
* KMC11, read registers:
*/
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_TRC, &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;
}
/*
* KMC11, 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_TRC, &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_TRC, &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 = data;
if (sel0 & SEL0_MRC) {
if (((sel0 & SEL0_RUN) == 0) && (changed & SEL0_RUN)) {
kmc_halt (k, HALT_MRC);
}
kmc_masterClear(&kmc_units[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_RDI;
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, we must service an input request even
* if we have another output command ready.
*/
if ((sel2 & SEL2_RDO) && (!(data & SEL2_RDO))) {
sel2 = data; /* RDO clearing, RDI can't be set */
if (sel0 & SEL0_RQI) {
sel2 |= SEL2_RDI;
kmc_updints(k);
} else
kmc_processCompletions(k);
} else {
if ((sel2 & SEL2_RDI) && (!(data & SEL2_RDI))) {
sel2 = data; /* RDI clearing, RDO can't be set */
kmc_dispatchInputCmd(k); /* Can set RDO */
if ((sel0 & SEL0_RQI) && !(sel2 & SEL2_RDO))
sel2 |= SEL2_RDI;
kmc_updints(k);
} else {
sel2 = data;
}
}
} else {
sel2 = data;
}
break;
case 02: /* SEL4 */
mna = data & (KMC_CRAMSIZE -1);
sel4 = data;
break;
case 03: /* SEL6 */
if ( sel0 & SEL0_RMI) {
mni = data;
}
sel6 = data;
break;
}
return SCPE_OK;
}
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_CMD, &kmc_dev, "KMC%u microcode start uPC %04o\n", k, upc);
break;
}
}
return;
}
/*
* KMC11 service routine:
*/
static t_stat kmc_eventService (UNIT* uptr) {
int dupidx;
int32 k = uptr - kmc_units;
assert ( (k >= 0) && (k < KMC_UNITS) );
/* Service all the DUPs assigned to this KMC */
for (dupidx = 0; dupidx < DUP_LINES; dupidx++) {
dupstate *d = &dupState[dupidx];
/* Only process enabled lines */
if ((d->kmc != k) || !(d->ctrlFlags & SEL6_CI_ENABLE)) {
continue;
}
kmc_updateDSR (d);
/* Feed transmitter */
/* Poll receiver */
}
/* Provide the illusion of progress. */
upc = 1 + ((upc + ((KMC_POLLTIME*1000)/KMC_CYCLETIME)) % (KMC_CRAMSIZE -1));
sim_activate_after(uptr, KMC_POLLTIME);
return SCPE_OK;
}
/*
* master clear a KMC
* Master clear initializes the logic, 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(UNIT *uptr) {
uint32 k;
k = uptr - kmc_units;
if (sim_deb) {
DEVICE *dptr = find_dev_from_unit (uptr);
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 (uint32 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) {
sim_activate_after (&kmc_units[k], KMC_POLLTIME);
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_LINE; 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;
d->line = MAX_LINE;
if ((d->kmc == k) && (d->dupidx != -1)) {
dup_set_DTR (i, FALSE);
dup_set_callback_mode (i, NULL, NULL, NULL);
}
d->dupidx = -1;
d->kmc = -1;
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->txstate = TXIDLE;
}
/* Completion queue */
initqueue( &cqueueHead, &cqueueCount, INIT_HDR_ONLY);
initqueue( &freecqHead, &freecqCount, MAX_LIST_SIZE(cqueue));
sim_activate_after (&kmc_units[k], KMC_POLLTIME);
gflags |= FLG_UCINI;
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.
*
* 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'. We don't implement this 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) {
int 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_CMD, &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, ba);
break;
case CMD_BASEIN: /* BASE IN. */
kmc_baseIn (k, d, cmdsel2, line, ba);
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. The DUP
* has 8 registers, so <2:1> must be zero. The other bits are reserved
* and must be zero.
*/
static void kmc_baseIn (int32 k, dupstate *d, uint16 cmdsel2, int line, uint32 ba ) {
uint16 csr;
uint32 csraddress;
/* Verify DUP is enabled and at specified address */
csraddress = sel6;
if ((csraddress & ~SEL6_II_DUPCSR) || (sel4 != 0) ||
(cmdsel2 & SEL2_II_RESERVED)) {
sim_debug (DF_CMD, &kmc_dev, "KMC%u: reserved bits set in BASE IN\n");
kmc_halt (k, HALT_BADCSR);
return;
}
csraddress += IOPAGEBASE;
/* Verify that the DUP is on-line and that its CSRs can be read.
* The hardware would probably discover this later.
*/
if (Map_ReadW (csraddress, 2, &csr)) {
sim_debug (DF_CMD, &kmc_dev, "KMC%u line%u: %060 0x%x DUP CSR0 NXM\n",
k, line, csraddress, csraddress);
kmc_ctrlOut (k, SEL6_CO_NXM, 0, line, 0); /* KMC would fail differently. */
return; /* This will cause OS to take action. */
}
ba = dup_csr_to_linenum (sel6);
if ((ba < 0) || (ba >= DIM(dupState))) {
sim_debug (DF_CMD, &kmc_dev, "KMC%u line%u: %06o 0x%x is not an enabled DUP\n",
k, line, csraddress, csraddress);
kmc_halt (k, HALT_BADDUP);
return;
}
d = &dupState[ba];
if ((d->kmc != -1) && (d->kmc != k)) {
sim_debug (DF_CMD, &kmc_dev, "KMC%u line%u: %06o 0x%x is already assigned to KMC%u\n",
k, line, csraddress, csraddress,
d->kmc);
kmc_halt (k, HALT_DUPALC);
return;
}
d->dupidx = ba;
d->line = line;
d->dupcsr = csraddress;
d->kmc = k;
line2dup[line] = d;
sim_debug(DF_CMD, &kmc_dev, "KMC%u line%u: DUP%u address=%06o 0x%x 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 is also used to enable/disable a line, which also sets/clears DTR.
*/
static void kmc_ctrlIn (int32 k, dupstate *d, int line, uint32 ba ) {
sim_debug(DF_CMD, &kmc_dev, "KMC%u line%u: DUP%d %s in %s duplex",
k, line, d->dupidx,
(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",
(sel6 & SEL6_CI_SADDR), line);
sim_debug(DF_CMD, &kmc_dev, " %s\n",
(sel6 & SEL6_CI_ENABLE)? "enabled":"disabled");
dup_set_DDCMP (d->dupidx, TRUE);
d->modemstate &= ~MDM_DSR; /* Initialize modem state reporting. */
d->ctrlFlags = sel6;
if (sel6 & SEL6_CI_ENABLE) {
dup_set_DTR (d->dupidx, TRUE);
dup_set_callback_mode (d->dupidx, kdp_receive, kmc_txComplete, kmc_modemChange);
} else {
dup_set_DTR (d->dupidx, FALSE);
dup_set_callback_mode (d->dupidx, NULL, kmc_txComplete, kmc_modemChange);
}
return;
}
/*
* RX BUFFER IN
*/
void kmc_rxBufferIn(dupstate *d, int32 ba, uint32 sel6v) {
int32 k = d->kmc;
BDL *qe;
uint32 bda = 0;
if (d->line == -1)
return;
if (!kmc_printBufferIn (k, &kmc_dev, d->line, "RX", d->rxavail, ba, sel6v))
return;
if (sel6v & SEL6_BI_KILL) {
uint16 bd[3];
/* Kill all current RX buffers. The DUP currently provides the entire message in
* one completion. So this is fairly simple. TODO: Ought to tell the DUP to resync.
*/
if (d->rxavail) {
qe = (BDL *)(d->rxqh.next);
bda = qe->ba;
/* Update bytes done 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.
* We don't necessarily have the bd cached in this case, so we re-read it here.
*/
if (!Map_ReadW (bda, 2*3, bd)) {
bd[1] = 0;
if (kmc_updateBDCount (bda, bd)) { ;/*
kmc_ctrlOut (k, SEL6_CO_NXM, 0, d->line, bda);
return;*/
}
}
}
while ((qe = (BDL *)remqueue (d->rxqh.next, &d->rxavail)) != NULL) {
assert (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, 0);
return;
}
}
if ((qe = (BDL *)remqueue (d->bdqh.next, &d->bdavail)) == NULL) {
kmc_halt (k, HALT_RCVOVF);
sim_debug(DF_QUEUE, &kmc_dev, "KMC%u line%u: Too many receive buffers from hostd\n", k, d->line);
return;
}
qe->ba = ba;
assert (insqueue( &qe->hdr, d->rxqh.prev, &d->rxavail, MAXQUEUE));
if (sel6v & SEL6_BI_KILL) { /* ENABLE is set too */
kmc_ctrlOut (k, SEL6_CO_KDONE, SEL2_IOT, d->line, bda);
}
return;
}
static void kdp_receive(int dupidx, uint8* data, int count) {
int32 k;
dupstate* d;
uint8 msgtyp;
WB wb;
assert ((dupidx >= 0) && (dupidx < DIM(dupState)));
d = &dupState[dupidx];
assert( dupidx == d->dupidx );
k = d->kmc;
wb.first = TRUE;
wb.dup = d;
wb.bd[1] = 0;
wb.bd[2] = BDL_LDS;
/* Flush messages too short to have a header, or with invalid msgtyp */
if( count < 8 )
return;
msgtyp = data[0];
if ( !((msgtyp == DDCMP_SOH) || (msgtyp == DDCMP_ENQ) || (msgtyp == DDCMP_DLE)) )
return;
/* Validate hcrc */
if (0 != ddcmp_crc16 (0, data, 8)) {
sim_debug(DF_QUEUE, &kmc_dev, "KMC%u line%u: HCRC Error for %d byte packet\n", k, d->line, count);
if (!kmc_rxWriteViaBDL (&wb, data, 6))
return;
kmc_ctrlOut (k, SEL6_CO_HCRC, SEL2_IOT, d->line, wb.bda);
return;
}
if (d->ctrlFlags & SEL6_CI_ENASS) {
if (!(data[5] == (d->ctrlFlags & SEL6_CI_SADDR))) { /* Also include SELECT? */
return;
}
}
if (!kmc_rxWriteViaBDL (&wb, data, 6))
return;
if (msgtyp != DDCMP_ENQ) {
/* The DUP has framed this mesage, so the length had better match
* what's in the header.
*/
assert( (((data[2] &~ 0300) << 8) | data[1]) == (count -10) );
if (!kmc_rxWriteViaBDL (&wb, data+8, count -(8+2)))
return;
if (0 != ddcmp_crc16 (0, data+8, count-8)) {
sim_debug(DF_QUEUE, &kmc_dev, "KMC%u line%u: data CRC error for %d byte packet\n", k, d->line, count);
kmc_ctrlOut (k, SEL6_CO_HCRC, SEL2_IOT, d->line, wb.bda);
return;
}
}
wb.bd[1] = wb.rcvc;
if (kmc_updateBDCount (wb.bda, wb.bd)) {
kmc_ctrlOut (k, SEL6_CO_NXM, SEL2_IOT, d->line, wb.bda);
return;
}
if (!kmc_bufferAddressOut (k, SEL6_BO_EOM, SEL2_IOT, d->line, wb.bda))
return;
return;
}
/*
* Here with a framed message to be delivered.
* The DUP has ensured that the data starts with a DDCMP MSGTYP,
* and that the entire message (thru BCC2) is in the data.
*
* In real hardware, the bytes would be processed one at a time, allowing
* an OS time to provide a new BDL if necessary. We can't do this with
* the kdp_receive callback - without a lot of extra complexity. So
* for this approximation, any lack of buffer is treated as an RX overrun.
*/
static t_bool kmc_rxWriteViaBDL( WB *wb, uint8 *data, int count ) {
int32 k = wb->dup->kmc;
while (count != 0) {
int seglen = count;
int32 xrem;
if ( wb->bd[1] == 0 ) {
BDL *bdl;
if (wb->bd[2] & BDL_LDS) {
dupstate *d = wb->dup;
if (!wb->first) {
wb->bd[1] = wb->rcvc;
if (kmc_updateBDCount (wb->bda, wb->bd)) {
kmc_ctrlOut (k, SEL6_CO_NXM, SEL2_IOT, wb->dup->line, wb->bda);
return FALSE;
}
if (!kmc_bufferAddressOut (k, 0, SEL2_IOT, wb->dup->line, wb->bda))
return FALSE;
wb->first = FALSE;
}
/* Get the first available buffer descriptor list, return to free queue */
if (!(bdl = (BDL *)remqueue(d->rxqh.next, &d->rxavail))) {
kmc_ctrlOut(k, SEL6_CO_NOBUF, SEL2_IOT, d->line, 0 );
return FALSE;
}
wb->bda = bdl->ba;
assert( insqueue (&bdl->hdr, d->bdqh.prev, &d->bdavail, DIM(d->bdq)) );
} else {
wb->bda += (3 * 2);
}
if (Map_ReadW (wb->bda, 3*2, wb->bd)) {
kmc_ctrlOut(k, SEL6_CO_NXM, SEL2_IOT, wb->dup->line, wb->bda);
return FALSE;
}
wb->ba = ((wb->bd[2] & 06000) << 6) | wb->bd[0];
if( wb->bd[2] == 0) {
kmc_halt (k, HALT_MTRCV);
sim_debug(DF_QUEUE, &kmc_dev, "KMC%u line%u: RX buffer descriptor size is zero\n", k, wb->dup->line);
return FALSE;
}
wb->rcvc = 0;
}
if (seglen > wb->bd[2] )
seglen = wb->bd[2];
xrem = Map_WriteB (wb->ba, seglen, data);
if (xrem != 0) {
uint16 bd[3];
memcpy (bd, &wb->bd, sizeof bd);
seglen -= xrem;
wb->rcvc += seglen;
bd[1] = wb->rcvc;
kmc_updateBDCount (wb->bda, bd); /* Unchecked because already reporting NXM */
kmc_ctrlOut (k, SEL6_CO_NXM, SEL2_IOT, wb->dup->line, wb->bda);
return FALSE;
}
wb->ba += seglen;
wb->rcvc += seglen;
count -= seglen;
data += seglen;
}
return TRUE;
}
/* Transmit */
/*
* Here with a bdl for some new transmit buffers.
* This has some timing/completion queue issues in
* degenerate cases. For now, assemble the whole
* message for the DUP and complete when we're told.
* If the DUP refuses a message, we drop it as we should
* only be offering a message when the DUP is idle. So
* Any problem represents a a hard error.
*/
void kmc_txBufferIn(dupstate *d, int32 ba, uint32 sel6v) {
int32 k = d->kmc;
BDL *qe;
if (d->line == -1)
return;
if (!kmc_printBufferIn (k, &kmc_dev, d->line, "TX", d->txavail, ba, sel6v))
return;
if (sel6v & SEL6_BI_KILL) {
/* Kill all current TX buffers. We can't abort the DUP 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.
*/
while ((qe = (BDL *)remqueue (d->txqh.next, &d->txavail)) != NULL) {
assert (insqueue (&qe->hdr, d->bdqh.prev, &d->bdavail, DIM(d->bdq)));
}
if (d->txstate == TXIDLE) {
if (!(sel6v & SEL6_BI_ENABLE)) {
kmc_ctrlOut (k, SEL6_CO_KDONE, 0, d->line, 0);
return;
}
} else {
if (sel6v & SEL6_BI_ENABLE)
d->txstate = TXKILR;
else {
d->txstate = TXKILL;
return;
}
}
}
if (!(qe = (BDL *)remqueue (d->bdqh.next, &d->bdavail))) {
kmc_halt (k, HALT_XMTOVF);
sim_debug(DF_QUEUE, &kmc_dev, "KMC%u line%u: Too many transmit buffers from host\n", k, d->line);
return;
}
qe->ba = ba;
assert (insqueue (&qe->hdr, d->txqh.prev, &d->txavail, MAXQUEUE));
if (d->txstate == TXIDLE)
kmc_txStartDup(d);
return;
}
/*
* Try to start DUP output. Does nothing if output is already in progress,
* or if there are no packets in the output queue.
*/
static void kmc_txStartDup(dupstate *d) {
int32 k = d->kmc;
while (TRUE) {
switch (d->txstate){
case TXIDLE:
if (!kmc_txNewBdl(d))
return;
d->txmlen = 0;
d->txstate = TXSOM;
case TXSOM:
if (!(d->tx.bd[2] & BDL_SOM)) {
kmc_halt (k, HALT_XSOM);
sim_debug(DF_QUEUE, &kmc_dev, "KMC%u line%u: TX BDL not SOM\n", k, d->line);
return;
}
d->txstate = TXHDR;
case TXHDR:
if (!kmc_txAppendBuffer(d))
return;
if (!(d->tx.bd[2] & BDL_EOM)) {
if (!kmc_txNewBd(d))
return;
continue;
}
if (d->txmsg[0] != DDCMP_ENQ) {
/* If the OS computes and includes HRC, this can
* be the last descriptor. In that case, this is EOM.
*/
if (d->tx.bd[2] & BDL_LDS) {
assert (d->tx.bd[2] & BDL_EOM);
assert (d->txmlen > 6);
d->txstate = TXACT;
if (!dup_put_msg_bytes (d->dupidx, d->txmsg, d->txmlen, TRUE, TRUE)) {
sim_debug(DF_QUEUE, &kmc_dev, "KMC%u line%u: DUP%d refused TX packet\n", k, d->line, d->dupidx);
}
return;
}
/* Data sent in a separate descriptor */
d->txstate = TXDATA;
d->tx.first = TRUE;
d->tx.bda += 6;
if (!kmc_txNewBd(d))
return;
if (!(d->tx.bd[2] & BDL_SOM)) {
kmc_halt (k, HALT_XSOM2);
sim_debug(DF_QUEUE, &kmc_dev, "KMC%u line%u: TX BDL not SOM\n", k, d->line);
return;
}
continue;
}
assert (d->txmlen == 6);
d->txstate = TXACT;
if (!dup_put_ddcmp_packet (d->dupidx, d->txmsg, d->txmlen)) {
sim_debug(DF_QUEUE, &kmc_dev, "KMC%u line%u: DUP%d refused TX packet\n", k, d->line, d->dupidx);
}
return;
case TXDATA:
if (!kmc_txAppendBuffer(d))
return;
if (!(d->tx.bd[2] & BDL_EOM)) {
if (!kmc_txNewBd(d))
return;
continue;
}
d->txstate = TXACT;
if (!dup_put_ddcmp_packet (d->dupidx, d->txmsg, d->txmlen)) {
sim_debug(DF_QUEUE, &kmc_dev, "KMC%u line%u: DUP%d refused TX packet\n", k, d->line, d->dupidx);
}
return;
default:
case TXACT:
sim_debug(DF_QUEUE, &kmc_dev, "KMC%u line%u: dup_start_output called while active\n", k, d->line);
return;
}
}
}
/* Transmit complete callback from the DUP
* Called with the last byte of a packet has been transmitted.
* Handle any deferred kill and start the next message.
*/
static void kmc_txComplete (int32 dupidx, int status) {
dupstate *d;
int32 k;
assert ((dupidx >= 0) && (dupidx < DIM(dupState)));
d = &dupState[dupidx];
k = d->kmc;
if (status) { /* Failure is probably due to modem state change */
kmc_updateDSR(d); /* Change does not stop transmission or release buffer */
}
d->txmlen = 0;
if ((d->txstate == TXKILL) || (d->txstate == TXKILR)) {
/* If we could kill a partial transmission, would update bd here */
d->txstate = TXIDLE;
kmc_ctrlOut (k, SEL6_CO_KDONE, 0, d->line, d->tx.bda);
} else {
if (d->tx.bd[2] & BDL_LDS)
d->txstate = TXIDLE;
else
d->txstate = TXSOM;
}
kmc_txStartDup(d);
}
/* Obtain a new buffer descriptor list from those queued by the host */
static t_bool kmc_txNewBdl(dupstate *d) {
int32 k = d->kmc;
BDL *qe;
if (!(qe = (BDL *)remqueue (d->txqh.next, &d->txavail))) {
return FALSE;
}
d->tx.bda = qe->ba;
assert (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.
*
* Release the current BD if there is one.
* If the current BD is the last of a list, request a new BDL.
*/
static t_bool kmc_txNewBd(dupstate *d) {
int32 k = d->kmc;
if (d->tx.first)
d->tx.first = FALSE;
else {
d->tx.bd[1] = d->tx.xmtc;
if (kmc_updateBDCount (d->tx.bda, d->tx.bd)) {
kmc_ctrlOut (k, SEL6_CO_NXM, 0, d->line, d->tx.bda);
return FALSE;
}
if (!kmc_bufferAddressOut (k, 0, 0, d->line, d->tx.bda))
return FALSE;
if (d->tx.bd[2] & BDL_LDS) {
if (!kmc_txNewBdl(d)) {
/* TODO??xmit_underrun notice?*/
return FALSE;
}
d->tx.first = FALSE;
} else
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] & 0006000) << 6) | d->tx.bd[0];
d->tx.xmtc = 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;
int32 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 = Map_ReadB (d->tx.ba, d->tx.bd[1], d->txmsg+d->txmlen);
d->tx.bd[1] -= rem;
rem += 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];
if (!kmc_bufferAddressOut (k, 0, 0, d->line, d->tx.bda))
return FALSE;
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;
}
assert( insqueue( &qe->hdr, freecqHead.prev, &freecqCount, CQUEUE_MAX ) );
sel2 = qe->bsel2;
sel4 = qe->bsel4;
sel6 = qe->bsel6;
sim_debug (DF_CMD, &kmc_dev, "KMC%u line%u: %s %s 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;
sim_debug (DF_QUEUE, &kmc_dev, "KMC%u line%u: enqueue %s CONTROL OUT Code=%02o Address=%06o\n",
k, line, rx? "RX":"TX", code, bda);
if (!(qe = (CQ *)remqueue( freecqHead.next, &freecqCount ))) {
sim_debug (DF_QUEUE, &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 & 0600000) >> (16-SEL6_V_CO_XAD)) | code;
assert (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) && (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? MDM_DSR : 0;
if (status != (d->modemstate & MDM_DSR)) {
d->modemstate = (d->modemstate &~MDM_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_QUEUE, &kmc_dev, "KMC%u line%u: enqueue %s BUFFER OUT Flags=%06o Address=%06o\n",
k, line, rx? "RX":"TX", flags, bda);
if (!kmc_printBDL(k, DF_QUEUE, &kmc_dev, line, bda, 2))
return FALSE;
if (!(qe = (CQ *)remqueue( freecqHead.next, &freecqCount ))) {
sim_debug (DF_QUEUE, &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 & 0600000) >> (16-SEL6_V_CO_XAD)) | flags;
assert (insqueue( &qe->hdr, cqueueHead.prev, &cqueueCount, CQUEUE_MAX ));
kmc_processCompletions(k);
return TRUE;
}
/* The KMC can only do byte NPRs, even for word quantities.
* We shortcut by writng words when updating the buffer descriptor's
* count field. 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, so I suspect that the KMC always writes the whole descriptor.
* That isn't sufficient: if the count is in the right half, writing
* the whole descriptor would trash the first word of the following
* descriptor. So, I suspect that the KMC must read the next descriptor
* (if the current one doesn't have LAST set) before updating the count.
* This means the cached copy corrects any trashing. Read before write
* also would tend to minimize the chance of under-run, so it's a reasonable
* guess. This is, er, awkward to emulate. As a compromise, we always
* write the count. We write the 3rd word iff the count is in the LH.
* This is guaranteed to restore the third word if it was trashed and
* not trash the following descriptor otherwise.
*/
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) {
if (error){
sel0 &= ~(SEL0_IEO|SEL0_IEI);
}
sel0 &= ~SEL0_RUN;
kmc_updints (k);
sim_cancel ( &kmc_units[k] );
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), we keep pending flags per KMC,
* and a global request count across KMCs fot the UBA.
* The UBA will clear the global request flag when it grants
* an interrupt; thus for set we always set the global flag
* 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_TRC, &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_TRC, &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_TRC, &kmc_dev, "KMC%u: set output interrupt\n", k);
gflags |= FLG_BINT;
BintPending++;
}
SET_INT(KMCB);
} else {
if (gflags & FLG_BINT) {
sim_debug(DF_TRC, &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.
* A given 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; k < DIM (kmc_gflags); k++) {
if (gflags & FLG_AINT) {
vec = kmc_dib.vec + (k*010);
gflags &= ~FLG_AINT;
if (--AintPending == 0) {
CLR_INT(KMCA);
}
}
}
sim_debug(DF_TRC, &kmc_dev, "KMC%u input (A) interrupt ack vector %030\n", k, vec);
return vec;
}
static int32 kmc_BintAck (void) {
int32 vec = 0; /* no interrupt request active */
int32 k;
for (k = 0; 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);
}
}
}
sim_debug(DF_TRC, &kmc_dev, "KMC%u output (B) interrupt ack vector %03o\n", k, vec);
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, int32 line, char *dir,
int32 count, int32 ba, uint16 sel6v) {
t_bool kill = ((sel6v & (SEL6_BI_KILL|SEL6_BI_ENABLE)) == SEL6_BI_KILL);
sim_debug(DF_CMD, &kmc_dev, "KMC%u line %u %s BUFER IN%s\n", k, line, dir, (kill? "(Buffer kill)": ""));
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, 1))
return FALSE;
sim_debug(DF_QUEUE, &kmc_dev, "KMC%u line %u: %s BUFFER IN %d, bdas=%06o 0x%04X\n", k, line, dir, count, ba, ba);
return TRUE;
}
/*
* Debug: Dump a BDL and a sample of its buffer.
*/
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;
}
sim_debug(dbits, dev, " bd[0] = %06o 0x%04X\n", bd[0], bd[0]);
sim_debug(dbits, dev, " bd[1] = %06o 0x%04X\n", bd[1], bd[1]);
sim_debug(dbits, dev, " bd[2] = %06o 0x%04X\n", bd[2], bd[2]);
if (prbuf) {
uint8 buf[20];
if (bd[1] > sizeof buf)
bd[1] = sizeof buf;
dp = bd[0] | ((bd[2] & 06000) << 6);
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 %060\n", k, line, dp);
return FALSE;
}
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))
break;
ba += 6;
}
return TRUE;
}
/* Verify that the microcode image is one we know 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.
*
* 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 were other, stranger microcodes.
*/
static const char *kmc_verifyUcode ( int32 k ) {
int 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_CMD, &kmc_dev, "KDP%u: Microcode not loaded\n", k );
return NULL;
}
for (i = 0; i < DIM (known); i++) {
if (crc == known[i].crc) {
sim_debug (DF_CMD, &kmc_dev, "KDP%u: %s microdcode loaded\n", k, known[i].name );
return known[i].name;
}
}
sim_debug (DF_CMD, &kmc_dev, "KDP%u: Unknown microdcode 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 uptr is that of unit zero.
*/
#if KMC_UNITS > 1
static t_stat kmc_setDeviceCount (UNIT *uptr, int32 val, char *cptr, void *desc) {
int32 newln;
uint32 dupidx;
t_stat r;
DEVICE *dptr = &kmc_dev;
for (dupidx = 0; dupidx < DIM (dupState); dupidx++) {
dupstate *d = &dupState[dupidx];
if ((d->kmc != -1) || (d->dupidx != -1)) {
return SCPE_ALATT;
}
}
if (cptr == NULL)
return SCPE_ARG;
newln = (int32) get_uint (cptr, 10, KMC_UNITS, &r);
if ((r != SCPE_OK) || (newln == 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 *uptr, int32 val, void *desc) {
DEVICE *dev = find_dev_from_unit(uptr);
if (dev->flags & DEV_DIS) {
fprintf (st, "Disabled");
return SCPE_OK;
}
fprintf (st, "devices=%d", dev->numunits);
return SCPE_OK;
}
#endif
/* Show KMC status */
t_stat kmc_showStatus ( FILE *st, UNIT *up, int32 v, void *dp) {
int32 k = up - kmc_units;
int32 line;
t_bool first = TRUE;
DEVICE *dev = find_dev_from_unit(up);
const char *ucname;
if (dev->flags & DEV_DIS) {
fprintf (st, "Disabled");
return SCPE_OK;
}
ucname = kmc_verifyUcode (k);
if (!(sel0 & SEL0_RUN)) {
fprintf (st, "%s halted at uPC %04o",
(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;
} else {
fprintf (st, "\n");
}
fprintf (st, " %3u %3u %06o %-8s %3s %s %s %s",
line, d->dupidx, d->dupcsr,
(d->ctrlFlags & SEL6_CI_ENABLE)? "enabled": "disabled",
(d->modemstate & MDM_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);
}
}
if (first)
fprintf (st, " No DUPs assigned");
return SCPE_OK;
}
/* Description of this device.
* Conventionally last function in the file.
*/
static char *kmc_description (DEVICE *dptr) {
return "KMC11-A Synchronous line controller supporting only COMM IOP/DUP microcode";
}