mirror of
https://github.com/simh/simh.git
synced 2026-01-13 15:27:14 +00:00
1336 lines
30 KiB
C
1336 lines
30 KiB
C
/* pdp11_kdp.c: KMC11/DUP11 Emulation
|
|
------------------------------------------------------------------------------
|
|
|
|
|
|
Written 2002 by Johnny Eriksson <bygg@stacken.kth.se>
|
|
|
|
Adapted to SIMH 3.? by Robert M. A. Jarratt in 2013
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
Modification history:
|
|
|
|
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 handle NXM on the unibus. At all. In fact, we don't
|
|
** generate control-outs.
|
|
**
|
|
** We don't really implement the DUP registers.
|
|
**
|
|
** We don't do anything but full-duplex DDCMP.
|
|
**
|
|
** We don't implement buffer flushing.
|
|
*/
|
|
|
|
#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
|
|
#define DUP_RDX 8
|
|
|
|
extern int32 IREQ (HLVL);
|
|
extern int32 tmxr_poll; /* calibrated delay */
|
|
extern int32 clk_tps; /* clock ticks per second */
|
|
extern int32 tmr_poll; /* instructions per tick */
|
|
|
|
#include "sim_tmxr.h"
|
|
|
|
#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 */
|
|
|
|
//t_stat sync_open(int* retval, char* cptr)
|
|
//{
|
|
// return SCPE_OK;
|
|
//}
|
|
int sync_read(int line, uint8* packet, int length)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
//void sync_write(int line, uint8* packet, int length)
|
|
//{
|
|
//}
|
|
|
|
//void sync_close(int line)
|
|
//{
|
|
//}
|
|
|
|
t_stat unibus_read(int32* data, int32 addr)
|
|
{
|
|
t_stat ans;
|
|
uint16 d;
|
|
|
|
*data = 0;
|
|
ans = Map_ReadW (addr, 2, &d);
|
|
*data = d;
|
|
return ans;
|
|
}
|
|
|
|
extern t_stat unibus_write(int32 data, int32 addr)
|
|
{
|
|
uint16 d;
|
|
|
|
d = data & 0xFFFF;
|
|
|
|
//printf("dma ub write 0x%08x=%06ho(oct)\n", addr, d);
|
|
return Map_WriteW (addr, 2, &d);
|
|
}
|
|
t_stat dma_write(int32 ba, uint8* data, int length)
|
|
{
|
|
t_stat r;
|
|
uint32 wd;
|
|
|
|
if (length <= 0) return SCPE_OK;
|
|
if (ba & 1) {
|
|
r = unibus_read((int32 *)&wd, ba-1);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
wd &= 0377;
|
|
wd |= (*data++ << 8);
|
|
r = unibus_write(wd, ba-1);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
length -= 1;
|
|
ba += 1;
|
|
}
|
|
while (length >= 2) {
|
|
wd = *data++;
|
|
wd |= *data++ << 8;
|
|
r = unibus_write(wd, ba);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
length -= 2;
|
|
ba += 2;
|
|
}
|
|
if (length == 1) {
|
|
r = unibus_read((int32 *)&wd, ba);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
wd &= 0177400;
|
|
wd |= *data++;
|
|
r = unibus_write(wd, ba);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* dma a block from main memory */
|
|
|
|
t_stat dma_read(int32 ba, uint8* data, int length)
|
|
{
|
|
t_stat r;
|
|
uint32 wd;
|
|
|
|
if (ba & 1) { /* Starting on an odd boundary? */
|
|
r = unibus_read((int32 *)&wd, ba-1);
|
|
if (r != SCPE_OK) {
|
|
return r;
|
|
}
|
|
*data++ = wd >> 8;
|
|
ba += 1;
|
|
length -= 1;
|
|
}
|
|
while (length > 0) {
|
|
r = unibus_read((int32 *)&wd, ba);
|
|
if (r != SCPE_OK) {
|
|
return r;
|
|
}
|
|
*data++ = wd & 0377;
|
|
if (length > 1) {
|
|
*data++ = wd >> 8;
|
|
}
|
|
ba += 2;
|
|
length -= 2;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* bits, sel0: */
|
|
|
|
#define KMC_RUN 0100000 /* Run bit. */
|
|
#define KMC_MRC 0040000 /* Master clear. */
|
|
#define KMC_CWR 0020000 /* CRAM write. */
|
|
#define KMC_SLU 0010000 /* Step Line Unit. */
|
|
#define KMC_LUL 0004000 /* Line Unit Loop. */
|
|
#define KMC_RMO 0002000 /* ROM output. */
|
|
#define KMC_RMI 0001000 /* ROM input. */
|
|
#define KMC_SUP 0000400 /* Step microprocessor. */
|
|
#define KMC_RQI 0000200 /* Request input. */
|
|
#define KMC_IEO 0000020 /* Interrupt enable output. */
|
|
#define KMC_IEI 0000001 /* Interrupt enable input. */
|
|
|
|
/* bits, sel2: */
|
|
|
|
#define KMC_OVR 0100000 /* Buffer overrun. */
|
|
#define KMC_LINE 0177400 /* Line number. */
|
|
#define KMC_RDO 0000200 /* Ready for output transaction. */
|
|
#define KMC_RDI 0000020 /* Ready for input transaction. */
|
|
#define KMC_IOT 0000004 /* I/O type, 1 = rx, 0 = tx. */
|
|
#define KMC_CMD 0000003 /* Command code. */
|
|
# 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. */
|
|
|
|
/* bits, sel6: */
|
|
|
|
#define BFR_EOM 0010000 /* End of message. */
|
|
#define BFR_KIL 0010000 /* Buffer Kill. */
|
|
|
|
/* 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. */
|
|
|
|
#ifndef MAXDUP
|
|
# define MAXDUP 2 /* Number of DUP-11's we can handle. */
|
|
#endif
|
|
|
|
#define MAXQUEUE 16 /* Number of rx bdl's we can handle. */
|
|
|
|
#define MAXMSG 2000 /* Largest message we handle. */
|
|
|
|
/* local variables: */
|
|
|
|
int kmc_running;
|
|
uint32 kmc_sel0;
|
|
uint32 kmc_sel2;
|
|
uint32 kmc_sel4;
|
|
uint32 kmc_sel6;
|
|
int kmc_rxi;
|
|
int kmc_txi;
|
|
|
|
uint16 kmc_microcode[KMC_CRAMSIZE];
|
|
|
|
uint32 dup_rxcsr[MAXDUP];
|
|
uint32 dup_rxdbuf[MAXDUP];
|
|
uint32 dup_parcsr[MAXDUP];
|
|
uint32 dup_txcsr[MAXDUP];
|
|
uint32 dup_txdbuf[MAXDUP];
|
|
|
|
struct dupblock {
|
|
uint32 rxqueue[MAXQUEUE]; /* Queue of bd's to receive into. */
|
|
uint32 rxcount; /* No. bd's in above. */
|
|
uint32 rxnext; /* Next bd to receive into. */
|
|
uint32 txqueue[MAXQUEUE]; /* Queue of bd's to transmit. */
|
|
uint32 txcount; /* No. bd's in above. */
|
|
uint32 txnext; /* Next bd to transmit. */
|
|
uint32 txnow; /* No. bd's we are transmitting now. */
|
|
uint8 txbuf[MAXMSG + 2]; /* contains next buffer to transmit, including two bytes for length */
|
|
uint8 txbuflen; /* length of message in buffer */
|
|
uint8 txbufbytessent;/* number of bytes from the message actually sent so far */
|
|
};
|
|
|
|
typedef struct dupblock dupblock;
|
|
|
|
dupblock dup[MAXDUP] = { 0 };
|
|
|
|
/* state/timing/etc: */
|
|
|
|
t_bool kmc_output = FALSE; /* Flag, need at least one output. */
|
|
int kmc_interval = 10000; /* Polling interval. */
|
|
|
|
TMLN kdp_ldsc[MAXDUP]; /* line descriptors */
|
|
TMXR kdp_desc[MAXDUP] =
|
|
{
|
|
{ 1, 0, 0, &kdp_ldsc[0] },
|
|
{ 1, 0, 0, &kdp_ldsc[1] }
|
|
};
|
|
|
|
extern int32 tmxr_poll; /* calibrated delay */
|
|
|
|
/* forward decls: */
|
|
|
|
t_stat kmc_rd(int32* data, int32 PA, int32 access);
|
|
t_stat kmc_wr(int32 data, int32 PA, int32 access);
|
|
int32 kmc_rxint (void);
|
|
int32 kmc_txint (void);
|
|
void kmc_setrxint();
|
|
void kmc_clrrxint();
|
|
void kmc_settxint();
|
|
void kmc_clrtxint();
|
|
t_stat kmc_svc(UNIT * uptr);
|
|
t_stat kmc_reset(DEVICE * dptr);
|
|
|
|
t_stat dup_rd(int32* data, int32 PA, int32 access);
|
|
t_stat dup_wr(int32 data, int32 PA, int32 access);
|
|
t_stat dup_svc(UNIT * uptr);
|
|
t_stat dup_reset(DEVICE * dptr);
|
|
t_stat dup_attach(UNIT * uptr, char * cptr);
|
|
t_stat dup_detach(UNIT * uptr);
|
|
void prbdl(uint32 dbits, DEVICE *dev, int32 ba, int prbuf);
|
|
|
|
t_stat send_packet(DEVICE *device, TMLN *lp, uint8 *buf, size_t size);
|
|
|
|
DEBTAB kmc_debug[] = {
|
|
{"CMD", DF_CMD},
|
|
{"TX", DF_TX},
|
|
{"RX", DF_RX},
|
|
{"DATA", DF_DATA},
|
|
{"QUEUE", DF_QUEUE},
|
|
{"TRC", DF_TRC},
|
|
{"INF", DF_INF},
|
|
{ "TMXRXMT", TMXR_DBG_XMT },
|
|
{ "TMXRRCV", TMXR_DBG_RCV },
|
|
{ "TMXRASY", TMXR_DBG_ASY },
|
|
{ "TMXRTRC", TMXR_DBG_TRC },
|
|
{ "TMXRCON", TMXR_DBG_CON },
|
|
{0}
|
|
};
|
|
|
|
/* KMC11 data structs: */
|
|
|
|
#define IOLN_KMC 010
|
|
|
|
DIB kmc_dib = { IOBA_AUTO, IOLN_KMC, &kmc_rd, &kmc_wr, 2, IVCL (KMCA), VEC_AUTO, {&kmc_rxint, &kmc_txint} };
|
|
|
|
UNIT kmc_unit = { UDATA (&kmc_svc, 0, 0) };
|
|
|
|
REG kmc_reg[] = {
|
|
{ ORDATA ( SEL0, kmc_sel0, 16) },
|
|
{ ORDATA ( SEL2, kmc_sel2, 16) },
|
|
{ ORDATA ( SEL4, kmc_sel4, 16) },
|
|
{ ORDATA ( SEL6, kmc_sel6, 16) },
|
|
|
|
{ ORDATA ( DEBUG, kmc_debug, 32) },
|
|
{ DRDATA ( INTERVAL, kmc_interval, 32) },
|
|
|
|
{ GRDATA (DEVADDR, kmc_dib.ba, KMC_RDX, 32, 0), REG_HRO },
|
|
/* { FLDATA (*DEVENB, kmc_dib.enb, 0), REG_HRO }, */
|
|
{ NULL },
|
|
};
|
|
|
|
MTAB kmc_mod[] = {
|
|
{ MTAB_XTD|MTAB_VDV, 010, "address", "ADDRESS",
|
|
&set_addr, &show_addr, NULL, "IP address" },
|
|
/* { MTAB_XTD|MTAB_VDV, 1, NULL, "ENABLED",
|
|
&set_enbdis, NULL, &kmc_dib },
|
|
{ MTAB_XTD|MTAB_VDV, 0, NULL, "DISABLED",
|
|
&set_enbdis, NULL, &kmc_dib },*/
|
|
{ MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "VECTOR", NULL,
|
|
&set_vec, &show_vec, NULL, "Interrupt vector" },
|
|
{ 0 },
|
|
};
|
|
|
|
DEVICE kmc_dev =
|
|
{
|
|
"KMC", &kmc_unit, kmc_reg, kmc_mod,
|
|
1, KMC_RDX, 13, 1, KMC_RDX, 8,
|
|
NULL, NULL, &kmc_reset,
|
|
NULL, NULL, NULL, &kmc_dib,
|
|
DEV_UBUS | DEV_DISABLE | DEV_DIS | DEV_DEBUG, 0, kmc_debug
|
|
};
|
|
|
|
/* DUP11 data structs: */
|
|
|
|
#define IOLN_DUP 010
|
|
|
|
DIB dup0_dib = { IOBA_AUTO, IOLN_DUP, &dup_rd, &dup_wr, 0 };
|
|
DIB dup1_dib = { IOBA_AUTO, IOLN_DUP, &dup_rd, &dup_wr, 0 };
|
|
|
|
UNIT dup_unit[MAXDUP] = {
|
|
{ UDATA (&dup_svc, UNIT_ATTABLE, 0) },
|
|
{ UDATA (&dup_svc, UNIT_ATTABLE, 0) }
|
|
};
|
|
|
|
REG dup0_reg[] =
|
|
{
|
|
{ GRDATA (DEVADDR, dup0_dib.ba, DUP_RDX, 32, 0), REG_HRO },
|
|
/* { FLDATA (*DEVENB, dup0_dib.enb, 0), REG_HRO },*/
|
|
{ NULL },
|
|
};
|
|
|
|
REG dup1_reg[] =
|
|
{
|
|
{ GRDATA (DEVADDR, dup1_dib.ba, DUP_RDX, 32, 0), REG_HRO },
|
|
/* { FLDATA (*DEVENB, dup1_dib.enb, 0), REG_HRO },*/
|
|
{ NULL },
|
|
};
|
|
|
|
MTAB dup_mod[] = {
|
|
{ MTAB_XTD|MTAB_VDV, 010, "address", "ADDRESS",
|
|
&set_addr, &show_addr },
|
|
/* { MTAB_XTD|MTAB_VDV, 1, NULL, "ENABLED",
|
|
&set_enbdis, NULL, &dup_dib },
|
|
{ MTAB_XTD|MTAB_VDV, 0, NULL, "DISABLED",
|
|
&set_enbdis, NULL, &dup_dib },*/
|
|
{ 0 },
|
|
};
|
|
|
|
DEVICE dup_dev[] =
|
|
{
|
|
{
|
|
"DUP0", &dup_unit[0], dup0_reg, dup_mod,
|
|
1, DUP_RDX, 13, 1, DUP_RDX, 8,
|
|
NULL, NULL, &dup_reset,
|
|
NULL, &dup_attach, &dup_detach, &dup0_dib,
|
|
DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG, 0, kmc_debug
|
|
},
|
|
{
|
|
"DUP1", &dup_unit[1], dup1_reg, dup_mod,
|
|
1, DUP_RDX, 13, 1, DUP_RDX, 8,
|
|
NULL, NULL, &dup_reset,
|
|
NULL, &dup_attach, &dup_detach, &dup1_dib,
|
|
DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG, 0, kmc_debug
|
|
}
|
|
};
|
|
|
|
t_stat send_buffer(int dupindex)
|
|
{
|
|
t_stat r = SCPE_OK;
|
|
dupblock* d;
|
|
|
|
d = &dup[dupindex];
|
|
|
|
if (d->txnow > 0 && kdp_ldsc[dupindex].conn)
|
|
{
|
|
r = send_packet(&dup_dev[dupindex], &kdp_ldsc[dupindex], d->txbuf, d->txbuflen);
|
|
d->txnext += d->txnow;
|
|
kmc_output = TRUE;
|
|
}
|
|
|
|
d->txnow = 0;
|
|
|
|
return r;
|
|
}
|
|
|
|
char *format_packet_data(uint8 *data, size_t size)
|
|
{
|
|
static char buf[3 * 128 + 1];
|
|
int buflen;
|
|
int i;
|
|
int n;
|
|
|
|
buflen = 0;
|
|
n = (size > 128) ? 128 : size;
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
sprintf(&buf[buflen], " %02X", data[i]);
|
|
buflen += 3;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
t_stat send_packet(DEVICE *device, TMLN *lp, uint8 *buf, size_t size)
|
|
{
|
|
int bytesLeft;
|
|
size_t i;
|
|
t_stat r = SCPE_OK;
|
|
|
|
sim_debug(DF_DATA, device, "Sending packet, length %d:%s\n", size - 2, format_packet_data(buf + 2, size - 2));
|
|
|
|
for (i=0; i<size; i++) {
|
|
r = tmxr_putc_ln (lp, buf[i]);
|
|
if (r != SCPE_OK) sim_debug(DF_DATA, device, "Failed to put a data byte\n");
|
|
}
|
|
bytesLeft = tmxr_send_buffered_data(lp);
|
|
if (bytesLeft != 0) sim_debug(DF_DATA, device, "Bytes left after send %d\n", bytesLeft);
|
|
|
|
return r;
|
|
}
|
|
|
|
int read_packet(DEVICE *device, TMLN *lp, uint8 *buf, size_t size)
|
|
{
|
|
size_t i;
|
|
size_t actualLength = 0;
|
|
int32 firstByte;
|
|
|
|
tmxr_poll_rx(lp->mp);
|
|
|
|
firstByte = tmxr_getc_ln(lp);
|
|
if (firstByte & TMXR_VALID)
|
|
{
|
|
actualLength = (firstByte & 0xFF) << 8;
|
|
actualLength += (tmxr_getc_ln(lp) & 0xFF);
|
|
|
|
if (actualLength > size)
|
|
{
|
|
sim_debug(DF_INF, device, "Received message too long, expected %d, but was %d\n", size, actualLength);
|
|
actualLength = 0;
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < actualLength; i++)
|
|
{
|
|
buf[i] = (uint8)(tmxr_getc_ln(lp) & 0xFF);
|
|
}
|
|
|
|
sim_debug(DF_DATA, device, "Read packet, length %d:%s\n", actualLength, format_packet_data(buf, actualLength));
|
|
}
|
|
}
|
|
|
|
return actualLength;
|
|
}
|
|
|
|
/*
|
|
** Update interrupt status:
|
|
*/
|
|
|
|
void kmc_updints(void)
|
|
{
|
|
if (kmc_sel0 & KMC_IEI) {
|
|
if (kmc_sel2 & KMC_RDI) {
|
|
kmc_setrxint();
|
|
} else {
|
|
kmc_clrrxint();
|
|
}
|
|
}
|
|
|
|
if (kmc_sel0 & KMC_IEO) {
|
|
if (kmc_sel2 & KMC_RDO) {
|
|
kmc_settxint();
|
|
} else {
|
|
kmc_clrtxint();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Try to set the RDO bit. If it can be set, set it and return true,
|
|
** else return false.
|
|
*/
|
|
|
|
t_bool kmc_getrdo(void)
|
|
{
|
|
if (kmc_sel2 & KMC_RDO) /* Already on? */
|
|
return FALSE;
|
|
if (kmc_sel2 & KMC_RDI) /* Busy doing input? */
|
|
return FALSE;
|
|
kmc_sel2 |= KMC_RDO;
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
** Try to do an output command.
|
|
*/
|
|
|
|
void kmc_tryoutput(void)
|
|
{
|
|
int i, j;
|
|
dupblock* d;
|
|
uint32 ba;
|
|
|
|
if (kmc_output) {
|
|
kmc_output = FALSE;
|
|
for (i = 0; i < MAXDUP; i += 1) {
|
|
d = &dup[i];
|
|
if (d->rxnext > 0) {
|
|
kmc_output = TRUE; /* At least one, need more scanning. */
|
|
if (kmc_getrdo()) {
|
|
ba = d->rxqueue[0];
|
|
kmc_sel2 &= ~KMC_LINE;
|
|
kmc_sel2 |= (i << 8);
|
|
kmc_sel2 &= ~KMC_CMD;
|
|
kmc_sel2 |= CMD_BUFFOUT;
|
|
kmc_sel2 |= KMC_IOT; /* Buffer type. */
|
|
kmc_sel4 = ba & 0177777;
|
|
kmc_sel6 = (ba >> 2) & 0140000;
|
|
kmc_sel6 |= BFR_EOM;
|
|
|
|
for (j = 1; j < (int)d->rxcount; j += 1) {
|
|
d->rxqueue[j-1] = d->rxqueue[j];
|
|
}
|
|
d->rxcount -= 1;
|
|
d->rxnext -= 1;
|
|
|
|
sim_debug(DF_QUEUE, &dup_dev[i], "DUP%d: (tryout) ba = %6o, rxcount = %d, rxnext = %d\r\n", i, ba, d->rxcount, d->rxnext);
|
|
kmc_updints();
|
|
}
|
|
return;
|
|
}
|
|
if (d->txnext > 0) {
|
|
kmc_output = TRUE; /* At least one, need more scanning. */
|
|
if (kmc_getrdo()) {
|
|
ba = d->txqueue[0];
|
|
kmc_sel2 &= ~KMC_LINE;
|
|
kmc_sel2 |= (i << 8);
|
|
kmc_sel2 &= ~KMC_CMD;
|
|
kmc_sel2 |= CMD_BUFFOUT;
|
|
kmc_sel2 &= ~KMC_IOT; /* Buffer type. */
|
|
kmc_sel4 = ba & 0177777;
|
|
kmc_sel6 = (ba >> 2) & 0140000;
|
|
|
|
for (j = 1; j < (int)d->txcount; j += 1) {
|
|
d->txqueue[j-1] = d->txqueue[j];
|
|
}
|
|
d->txcount -= 1;
|
|
d->txnext -= 1;
|
|
|
|
sim_debug(DF_QUEUE, &dup_dev[i], "DUP%d: (tryout) ba = %6o, txcount = %d, txnext = %d\r\n", i, ba, d->txcount, d->txnext);
|
|
kmc_updints();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Try to start output. Does nothing if output is already in progress,
|
|
** or if there are no packets in the output queue.
|
|
*/
|
|
|
|
void dup_tryxmit(int dupindex)
|
|
{
|
|
dupblock* d;
|
|
|
|
int pos; /* Offset into transmit buffer. */
|
|
|
|
uint32 bda; /* Buffer Descriptor Address. */
|
|
uint32 bd[3]; /* Buffer Descriptor. */
|
|
uint32 bufaddr; /* Buffer Address. */
|
|
uint32 buflen; /* Buffer Length. */
|
|
|
|
int msglen; /* Message length. */
|
|
int dcount; /* Number of descriptors to use. */
|
|
t_bool lds; /* Found last descriptor. */
|
|
|
|
int i; /* Random loop var. */
|
|
int delay; /* Estimated transmit time. */
|
|
|
|
extern int32 tmxr_poll; /* calibrated delay */
|
|
|
|
d = &dup[dupindex];
|
|
|
|
if (d->txnow > 0) return; /* If xmit in progress, quit. */
|
|
if (d->txcount <= d->txnext) return;
|
|
|
|
/*
|
|
** check the transmit buffers we have queued up and find out if
|
|
** we have a full DDCMP frame.
|
|
*/
|
|
|
|
lds = FALSE; /* No last descriptor yet. */
|
|
dcount = msglen = 0; /* No data yet. */
|
|
|
|
/* accumulate length, scan for LDS flag */
|
|
|
|
for (i = d->txnext; i < (int)d->txcount; i += 1) {
|
|
bda = d->txqueue[i];
|
|
(void) unibus_read((int32 *)&bd[0], bda);
|
|
(void) unibus_read((int32 *)&bd[1], bda + 2);
|
|
(void) unibus_read((int32 *)&bd[2], bda + 4);
|
|
|
|
dcount += 1; /* Count one more descriptor. */
|
|
msglen += bd[1]; /* Count some more bytes. */
|
|
if (bd[2] & BDL_LDS) {
|
|
lds = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!lds) return; /* If no end of message, give up. */
|
|
|
|
d->txnow = dcount; /* Got a full frame, will send or ignore it. */
|
|
|
|
if (msglen <= MAXMSG) { /* If message fits in buffer, - */
|
|
d->txbuf[0] = (uint8)(msglen>>8) & 0xFF;
|
|
d->txbuf[1] = (uint8)msglen & 0xFF;
|
|
d->txbuflen = msglen + 2;
|
|
pos = 2;
|
|
|
|
for (i = d->txnext; i < (int)(d->txnext + dcount); i += 1) {
|
|
bda = d->txqueue[i];
|
|
(void) unibus_read((int32 *)&bd[0], bda);
|
|
(void) unibus_read((int32 *)&bd[1], bda + 2);
|
|
(void) unibus_read((int32 *)&bd[2], bda + 4);
|
|
|
|
bufaddr = bd[0] + ((bd[2] & 06000) << 6);
|
|
buflen = bd[1];
|
|
|
|
(void) dma_read(bufaddr, &d->txbuf[pos], buflen);
|
|
pos += buflen;
|
|
}
|
|
|
|
send_buffer(dupindex);
|
|
}
|
|
|
|
#define IPS() (tmxr_poll * 50) /* UGH! */
|
|
|
|
/*
|
|
** Delay calculation:
|
|
** delay (instructions) = bytes * IPS * 8 / speed;
|
|
** either do this in floating point, or be very careful about
|
|
** overflows...
|
|
*/
|
|
|
|
delay = IPS() / (19200 >> 10);
|
|
delay *= msglen;
|
|
delay >>= 7;
|
|
|
|
//sim_activate(&dup_unit[dupindex], delay);
|
|
}
|
|
|
|
/*
|
|
** Here with a bdl for some new receive buffers. Set them up.
|
|
*/
|
|
|
|
void dup_newrxbuf(int line, int32 ba)
|
|
{
|
|
dupblock* d;
|
|
int32 w3;
|
|
|
|
d = &dup[line];
|
|
|
|
for (;;) {
|
|
if (d->rxcount < MAXQUEUE) {
|
|
d->rxqueue[d->rxcount] = ba;
|
|
d->rxcount += 1;
|
|
sim_debug(DF_QUEUE, &dup_dev[line], "Queued rx buffer %d, descriptor address=0x%04X(%06o octal)\n", d->rxcount - 1, ba, ba);
|
|
}
|
|
else
|
|
{
|
|
sim_debug(DF_QUEUE, &dup_dev[line], "(newrxb) no more room for buffers\n");
|
|
}
|
|
|
|
(void) unibus_read(&w3, ba + 4);
|
|
if (w3 & BDL_LDS)
|
|
break;
|
|
|
|
ba += 6;
|
|
}
|
|
|
|
sim_debug(DF_QUEUE, &dup_dev[line], "(newrxb) rxcount = %d, rxnext = %d\n", d->rxcount, d->rxnext);
|
|
|
|
}
|
|
|
|
/*
|
|
** Here with a bdl for some new transmit buffers. Set them up and then
|
|
** try to start output if not already active.
|
|
*/
|
|
|
|
void dup_newtxbuf(int line, int32 ba)
|
|
{
|
|
dupblock* d;
|
|
int32 w3;
|
|
|
|
d = &dup[line];
|
|
|
|
for (;;) {
|
|
if (d->txcount < MAXQUEUE) {
|
|
d->txqueue[d->txcount] = ba;
|
|
d->txcount += 1;
|
|
}
|
|
(void) unibus_read(&w3, ba + 4);
|
|
if (w3 & BDL_LDS)
|
|
break;
|
|
|
|
ba += 6;
|
|
}
|
|
|
|
sim_debug(DF_QUEUE, &dup_dev[line], "DUP%d: (newtxb) txcount = %d, txnext = %d\r\n", line, d->txcount, d->txnext);
|
|
|
|
dup_tryxmit(line); /* Try to start output. */
|
|
}
|
|
|
|
/*
|
|
** Here to store a block of data into a receive buffer.
|
|
*/
|
|
|
|
void dup_receive(int line, uint8* data, int count)
|
|
{
|
|
dupblock* d;
|
|
uint32 bda;
|
|
uint32 bd[3];
|
|
uint32 ba;
|
|
uint32 bl;
|
|
|
|
d = &dup[line];
|
|
|
|
if (d->rxcount > d->rxnext) {
|
|
bda = d->rxqueue[d->rxnext];
|
|
(void) unibus_read((int32 *)&bd[0], bda);
|
|
(void) unibus_read((int32 *)&bd[1], bda + 2);
|
|
(void) unibus_read((int32 *)&bd[2], bda + 4);
|
|
sim_debug(DF_QUEUE, &dup_dev[line], "dup_receive ba=0x%04x(%06o octal). Descriptor is:\n", bda, bda);
|
|
prbdl(DF_QUEUE, &dup_dev[line], bda, 0);
|
|
|
|
ba = bd[0] + ((bd[2] & 06000) << 6);
|
|
bl = bd[1];
|
|
|
|
if (count > (int)bl) count = bl; /* XXX */
|
|
|
|
sim_debug(DF_QUEUE, &dup_dev[line], "Receive buf[%d] writing to address=0x%04X(%06o octal), bytes=%d\n", d->rxnext, ba, ba, count);
|
|
(void) dma_write(ba, data, count);
|
|
|
|
bd[2] |= (BDL_SOM | BDL_EOM);
|
|
|
|
(void) unibus_write(bd[2], bda + 4);
|
|
|
|
d->rxnext += 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Try to receive data for a given line:
|
|
*/
|
|
|
|
void dup_tryreceive(int dupindex)
|
|
{
|
|
int length;
|
|
uint8 buffer[MAXMSG];
|
|
|
|
if (kdp_ldsc[dupindex].conn) { /* Got a sync line? */
|
|
length = read_packet(&dup_dev[dupindex], &kdp_ldsc[dupindex], buffer, MAXMSG);
|
|
if (length > 0) { /* Got data? */
|
|
sim_debug(DF_RX, &dup_dev[dupindex], "DUP%d: receiving %d bytes\r\n", dupindex, length);
|
|
dup_receive(dupindex, buffer, length);
|
|
kmc_output = TRUE; /* Flag this. */
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** testing testing
|
|
*/
|
|
|
|
void prbdl(uint32 dbits, DEVICE *dev, int32 ba, int prbuf)
|
|
{
|
|
int32 w1, w2, w3;
|
|
int32 dp;
|
|
|
|
for (;;) {
|
|
(void) unibus_read(&w1, ba);
|
|
(void) unibus_read(&w2, ba + 2);
|
|
(void) unibus_read(&w3, ba + 4);
|
|
|
|
sim_debug(dbits, dev, " Word 1 = 0x%04X(%06o octal)\n", w1, w1);
|
|
sim_debug(dbits, dev, " Word 2 = 0x%04X(%06o octal)\n", w2, w2);
|
|
sim_debug(dbits, dev, " Word 3 = 0x%04X(%06o octal)\n", w3, w3);
|
|
|
|
if (prbuf) {
|
|
if (w2 > 20) w2 = 20;
|
|
dp = w1 + ((w3 & 06000) << 6);
|
|
|
|
while (w2 > 0) {
|
|
(void) unibus_read(&w1, dp);
|
|
dp += 2;
|
|
w2 -= 2;
|
|
|
|
sim_debug(DF_CMD, dev, " %2x %2x", w1 & 0xff, w1 >> 8);
|
|
}
|
|
sim_debug(DF_CMD, dev, "\r\n");
|
|
}
|
|
if (w3 & BDL_LDS) break;
|
|
ba += 6;
|
|
}
|
|
}
|
|
|
|
void kmc_setrxint()
|
|
{
|
|
sim_debug(DF_TRC, &kmc_dev, "set rx interrupt\n");
|
|
kmc_rxi = 1;
|
|
SET_INT(KMCA);
|
|
}
|
|
|
|
void kmc_clrrxint()
|
|
{
|
|
sim_debug(DF_TRC, &kmc_dev, "clear rx interrupt\n");
|
|
kmc_rxi = 0;
|
|
CLR_INT(KMCA);
|
|
}
|
|
|
|
void kmc_settxint()
|
|
{
|
|
sim_debug(DF_TRC, &kmc_dev, "set tx interrupt\n");
|
|
kmc_txi = 1;
|
|
SET_INT(KMCB);
|
|
}
|
|
|
|
void kmc_clrtxint()
|
|
{
|
|
sim_debug(DF_TRC, &kmc_dev, "clear tx interrupt\n");
|
|
kmc_txi = 0;
|
|
CLR_INT(KMCB);
|
|
}
|
|
|
|
/*
|
|
** Here to perform an input command:
|
|
*/
|
|
|
|
void kmc_doinput(void)
|
|
{
|
|
int line;
|
|
int32 ba;
|
|
|
|
line = (kmc_sel2 & 077400) >> 8;
|
|
ba = ((kmc_sel6 & 0140000) << 2) + kmc_sel4;
|
|
|
|
sim_debug(DF_CMD, &kmc_dev, "Input command: sel2=%06o sel4=%06o sel6=%06o\n", kmc_sel2, kmc_sel4, kmc_sel6);
|
|
sim_debug(DF_CMD, &kmc_dev, "Line %d ba=0x%04x(%06o octal)\n", line, ba, ba);
|
|
switch (kmc_sel2 & 7) {
|
|
case 0:
|
|
sim_debug(DF_CMD, &kmc_dev, "Descriptor for tx buffer:\n");
|
|
prbdl(DF_CMD, &kmc_dev, ba, 1);
|
|
break;
|
|
case 4:
|
|
sim_debug(DF_CMD, &kmc_dev, "Descriptor for rx buffer:\n");
|
|
prbdl(DF_CMD, &kmc_dev, ba, 0);
|
|
}
|
|
|
|
switch (kmc_sel2 & 7) {
|
|
case 0: /* Buffer in, data to send: */
|
|
dup_newtxbuf(line, ba);
|
|
break;
|
|
case 1: /* Control in. */
|
|
/*
|
|
** The only thing this does is tell us to run DDCMP, in full duplex,
|
|
** but that is the only thing we know how to do anyway...
|
|
*/
|
|
break;
|
|
case 3: /* Base in. */
|
|
/*
|
|
** The only thing this does is tell the KMC what unibus address
|
|
** the dup is at. But we already know...
|
|
*/
|
|
break;
|
|
case 4: /* Buffer in, receive buffer for us... */
|
|
dup_newrxbuf(line, ba);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** master clear the KMC:
|
|
*/
|
|
|
|
void kmc_mclear(void)
|
|
{
|
|
int i;
|
|
dupblock* d;
|
|
|
|
sim_debug(DF_INF, &kmc_dev, "Master clear\n");
|
|
kmc_running = 0;
|
|
kmc_sel0 = KMC_MRC;
|
|
kmc_sel2 = 0;
|
|
kmc_sel4 = 0;
|
|
kmc_sel6 = 0;
|
|
kmc_rxi = 0;
|
|
kmc_txi = 0;
|
|
|
|
/* clear out the dup's as well. */
|
|
|
|
for (i = 0; i < MAXDUP; i += 1) {
|
|
d = &dup[i];
|
|
d->rxcount = 0;
|
|
d->rxnext = 0;
|
|
d->txcount = 0;
|
|
d->txnext = 0;
|
|
d->txnow = 0;
|
|
sim_cancel(&dup_unit[i]); /* Stop xmit wait. */
|
|
sim_clock_coschedule(&dup_unit[i], tmxr_poll);
|
|
}
|
|
sim_cancel(&kmc_unit); /* Stop the clock. */
|
|
sim_clock_coschedule(&kmc_unit, tmxr_poll);
|
|
}
|
|
|
|
/*
|
|
** DUP11, read registers:
|
|
*/
|
|
|
|
t_stat dup_rd(int32* data, int32 PA, int32 access)
|
|
{
|
|
int dupno;
|
|
|
|
dupno = ((PA - dup0_dib.ba) >> 3) & (MAXDUP - 1);
|
|
|
|
switch ((PA >> 1) & 03) {
|
|
case 00:
|
|
*data = dup_rxcsr[dupno];
|
|
break;
|
|
case 01:
|
|
*data = dup_rxdbuf[dupno];
|
|
break;
|
|
case 02:
|
|
*data = dup_txcsr[dupno];
|
|
break;
|
|
case 03:
|
|
*data = dup_txdbuf[dupno];
|
|
break;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/*
|
|
** KMC11, read registers:
|
|
*/
|
|
|
|
t_stat kmc_rd(int32* data, int32 PA, int32 access)
|
|
{
|
|
switch ((PA >> 1) & 03) {
|
|
case 00:
|
|
*data = kmc_sel0;
|
|
break;
|
|
case 01:
|
|
*data = kmc_sel2;
|
|
break;
|
|
case 02:
|
|
*data = kmc_sel4;
|
|
break;
|
|
case 03:
|
|
if (kmc_sel0 == KMC_RMO)
|
|
{
|
|
kmc_sel6 = kmc_microcode[kmc_sel4 & (KMC_CRAMSIZE - 1)];
|
|
}
|
|
*data = kmc_sel6;
|
|
break;
|
|
}
|
|
|
|
sim_debug(DF_TRC, &kmc_dev, "kmc_rd(), addr=0%o access=%d, result=0x%04x\n", PA, access, *data);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/*
|
|
** DUP11, write registers:
|
|
*/
|
|
|
|
t_stat dup_wr(int32 data, int32 PA, int32 access)
|
|
{
|
|
int dupno;
|
|
|
|
dupno = ((PA - dup0_dib.ba) >> 3) & (MAXDUP - 1);
|
|
|
|
switch ((PA >> 1) & 03) {
|
|
case 00:
|
|
dup_rxcsr[dupno] = data;
|
|
break;
|
|
case 01:
|
|
dup_parcsr[dupno] = data;
|
|
break;
|
|
case 02:
|
|
dup_txcsr[dupno] = data;
|
|
break;
|
|
case 03:
|
|
dup_txdbuf[dupno] = data;
|
|
break;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
void kmc_domicroinstruction()
|
|
{
|
|
static uint32 save;
|
|
if (kmc_sel6 == 041222) /* MOVE <MEM><BSEL2> */
|
|
{
|
|
kmc_sel2 = (kmc_sel2 & ~0xFF) | (save & 0xFF);
|
|
}
|
|
else if (kmc_sel6 == 0122440) /* MOVE <BSEL2><MEM> */
|
|
{
|
|
save = kmc_sel2 & 0xFF;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** KMC11, write registers:
|
|
*/
|
|
|
|
t_stat kmc_wr(int32 data, int32 PA, int32 access)
|
|
{
|
|
uint32 toggle;
|
|
int reg = PA & 07;
|
|
int sel = (PA >> 1) & 03;
|
|
|
|
if (access == WRITE)
|
|
{
|
|
sim_debug(DF_TRC, &kmc_dev, "kmc_wr(), addr=0%08o, SEL%d, data=0x%04x\n", PA, reg, data);
|
|
}
|
|
else
|
|
{
|
|
sim_debug(DF_TRC, &kmc_dev, "kmc_wr(), addr=0x%08o, BSEL%d, data=%04x\n", PA, reg, data);
|
|
}
|
|
|
|
switch (sel) {
|
|
case 00:
|
|
if (access == WRITEB) {
|
|
data = (PA & 1)
|
|
? (((data & 0377) << 8) | (kmc_sel0 & 0377))
|
|
: ((data & 0377) | (kmc_sel0 & 0177400));
|
|
}
|
|
toggle = kmc_sel0 ^ data;
|
|
kmc_sel0 = data;
|
|
if (kmc_sel0 & KMC_MRC) {
|
|
kmc_mclear();
|
|
break;
|
|
}
|
|
if ((toggle & KMC_CWR) && (toggle & KMC_RMO) && !(data & KMC_CWR) && !(data & KMC_RMO)) {
|
|
kmc_microcode[kmc_sel4 & (KMC_CRAMSIZE - 1)] = kmc_sel6;
|
|
}
|
|
|
|
if ((toggle & KMC_RMI) && (toggle & KMC_SUP) && !(data & KMC_RMI) && !(data & KMC_SUP))
|
|
{
|
|
kmc_domicroinstruction();
|
|
}
|
|
|
|
if (toggle & KMC_RUN) { /* Changing the run bit? */
|
|
if (kmc_sel0 & KMC_RUN)
|
|
{
|
|
sim_debug(DF_INF, &kmc_dev, "Started RUNing\n");
|
|
kmc_running = 1;
|
|
}
|
|
else
|
|
{
|
|
sim_debug(DF_INF, &kmc_dev, "Stopped RUNing\n");
|
|
sim_cancel(&kmc_unit);
|
|
kmc_running = 0;
|
|
}
|
|
}
|
|
break;
|
|
case 01:
|
|
if (access == WRITEB) {
|
|
data = (PA & 1)
|
|
? (((data & 0377) << 8) | (kmc_sel2 & 0377))
|
|
: ((data & 0377) | (kmc_sel2 & 0177400));
|
|
}
|
|
if (kmc_running)
|
|
{
|
|
if ((kmc_sel2 & KMC_RDI) && (!(data & KMC_RDI))) {
|
|
kmc_sel2 = data;
|
|
kmc_doinput();
|
|
} else if ((kmc_sel2 & KMC_RDO) && (!(data & KMC_RDO))) {
|
|
kmc_sel2 = data;
|
|
kmc_tryoutput();
|
|
} else {
|
|
kmc_sel2 = data;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
kmc_sel2 = data;
|
|
}
|
|
break;
|
|
case 02:
|
|
if (kmc_sel0 & KMC_RMO) {
|
|
kmc_sel6 = kmc_microcode[data & (KMC_CRAMSIZE - 1)];
|
|
}
|
|
kmc_sel4 = data;
|
|
break;
|
|
case 03:
|
|
kmc_sel6 = data;
|
|
break;
|
|
}
|
|
|
|
if (kmc_running)
|
|
{
|
|
if (kmc_output) {
|
|
kmc_tryoutput();
|
|
}
|
|
if (kmc_sel0 & KMC_RQI) {
|
|
if (!(kmc_sel2 & KMC_RDO)) {
|
|
kmc_sel2 |= KMC_RDI;
|
|
}
|
|
}
|
|
|
|
kmc_updints();
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
int32 kmc_rxint (void)
|
|
{
|
|
int32 ans = 0; /* no interrupt request active */
|
|
if (kmc_rxi != 0)
|
|
{
|
|
ans = kmc_dib.vec;
|
|
kmc_clrrxint();
|
|
}
|
|
|
|
sim_debug(DF_TRC, &kmc_dev, "rx interrupt ack %d\n", ans);
|
|
|
|
return ans;
|
|
}
|
|
|
|
int32 kmc_txint (void)
|
|
{
|
|
int32 ans = 0; /* no interrupt request active */
|
|
if (kmc_txi != 0)
|
|
{
|
|
ans = kmc_dib.vec + 4;
|
|
kmc_clrtxint();
|
|
}
|
|
|
|
sim_debug(DF_TRC, &kmc_dev, "tx interrupt ack %d\n", ans);
|
|
|
|
return ans;
|
|
}
|
|
|
|
/*
|
|
** DUP11 service routine:
|
|
*/
|
|
|
|
t_stat dup_svc(UNIT* uptr)
|
|
{
|
|
int dupindex;
|
|
int32 ln;
|
|
dupblock* d;
|
|
|
|
dupindex = uptr->u3;
|
|
d = &dup[dupindex];
|
|
//printf("dup_svc %d\n", dupindex);
|
|
|
|
ln = tmxr_poll_conn(&kdp_desc[dupindex]);
|
|
if (ln >= 0)
|
|
{
|
|
kdp_ldsc[dupindex].rcve = 1;
|
|
}
|
|
|
|
tmxr_poll_rx(&kdp_desc[dupindex]);
|
|
tmxr_poll_tx(&kdp_desc[dupindex]);
|
|
|
|
//send_buffer(dupindex);
|
|
//if (d->txnow > 0) {
|
|
// d->txnext += d->txnow;
|
|
// d->txnow = 0;
|
|
// kmc_output = TRUE;
|
|
//}
|
|
|
|
if (d->txcount > d->txnext) {
|
|
dup_tryxmit(dupindex);
|
|
}
|
|
|
|
sim_clock_coschedule (uptr, tmxr_poll);
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/*
|
|
** KMC11 service routine:
|
|
*/
|
|
|
|
t_stat kmc_svc (UNIT* uptr)
|
|
{
|
|
int i;
|
|
int dupno;
|
|
|
|
dupno = uptr->u3;
|
|
|
|
for (i = 0; i < MAXDUP; i += 1) {
|
|
dup_tryreceive(i);
|
|
}
|
|
if (kmc_output) {
|
|
kmc_tryoutput(); /* Try to do an output transaction. */
|
|
}
|
|
sim_clock_coschedule (uptr, tmxr_poll);
|
|
//sim_activate(&kmc_unit, kmc_interval);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/*
|
|
** DUP11, reset device:
|
|
*/
|
|
|
|
t_stat dup_reset(DEVICE* dptr)
|
|
{
|
|
// static t_bool firsttime = TRUE;
|
|
int i;
|
|
|
|
for (i = 0; i < MAXDUP; i++)
|
|
{
|
|
dup_unit[i].u3 = i;
|
|
kdp_ldsc[i].rcve = 1;
|
|
}
|
|
|
|
//if (firsttime) {
|
|
// for (i = 1; i < MAXDUP; i += 1) {
|
|
// dup_unit[i] = dup_unit[0]; /* Copy all the units. */
|
|
// }
|
|
// for (i = 0; i < MAXDUP; i += 1) {
|
|
// //tmxr_reset_ln(&kdp_ldsc[i]);
|
|
// dup_unit[i].u3 = i; /* Link dupblock to unit. */
|
|
// }
|
|
// firsttime = FALSE; /* Once-only init done now. */
|
|
//}
|
|
|
|
return auto_config (dptr->name, (dptr->flags & DEV_DIS)? 0: 1 ); /* auto config */
|
|
}
|
|
|
|
/*
|
|
** KMC11, reset device:
|
|
*/
|
|
|
|
t_stat kmc_reset(DEVICE* dptr)
|
|
{
|
|
kmc_sel0 = 0;
|
|
kmc_sel2 = 0;
|
|
kmc_sel4 = 0;
|
|
kmc_sel6 = 0;
|
|
|
|
return auto_config (dptr->name, (dptr->flags & DEV_DIS)? 0: 1 ); /* auto config */
|
|
}
|
|
|
|
/*
|
|
** DUP11, attach device:
|
|
*/
|
|
|
|
t_stat dup_attach(UNIT* uptr, char* cptr)
|
|
{
|
|
int dupno;
|
|
t_stat r;
|
|
char* tptr;
|
|
|
|
dupno = uptr->u3;
|
|
|
|
tptr = (char *)malloc(strlen(cptr) + 1);
|
|
if (tptr == NULL) return SCPE_MEM;
|
|
strcpy(tptr, cptr);
|
|
|
|
r = tmxr_attach(&kdp_desc[dupno], uptr, cptr);
|
|
if (r != SCPE_OK) {
|
|
free(tptr);
|
|
return r;
|
|
}
|
|
|
|
uptr->filename = tptr;
|
|
uptr->flags |= UNIT_ATT;
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/*
|
|
** DUP11, detach device:
|
|
*/
|
|
|
|
t_stat dup_detach(UNIT* uptr)
|
|
{
|
|
int dupno;
|
|
|
|
dupno = uptr->u3;
|
|
|
|
tmxr_detach(&kdp_desc[dupno], uptr);
|
|
|
|
if (uptr->flags & UNIT_ATT) {
|
|
free(uptr->filename);
|
|
uptr->filename = NULL;
|
|
uptr->flags &= ~UNIT_ATT;
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|